C #에서 개체를 바이트 배열로 변환하는 방법


99

바이너리 파일에 쓰는 데 필요한 개체 모음이 있습니다.

파일의 바이트를 압축해야하므로 BinaryFormatter. BinaryFormatter역 직렬화 요구에 대한 모든 종류의 정보를 제공합니다.

내가 시도하면

byte[] myBytes = (byte[]) myObject 

런타임 예외가 발생합니다.

나는 이것이 빨라야하므로 오히려 바이트 배열을 복사하지 않을 것입니다. 캐스트 byte[] myBytes = (byte[]) myObject가 일하기를 바랍니다!

OK 단지 명확하게하기 위해, 내가 가질 수 있는 출력 파일에 메타 데이터를. 객체 바이트 만. 패킹 된 객체 대 객체. 받은 답변에 따르면 저수준 Buffer.BlockCopy코드를 작성하는 것 같습니다 . 안전하지 않은 코드를 사용하고있을 수 있습니다.

답변:


173

객체를 바이트 배열로 변환하려면 :

// Convert an object to a byte array
public static byte[] ObjectToByteArray(Object obj)
{
    BinaryFormatter bf = new BinaryFormatter();
    using (var ms = new MemoryStream())
    {
        bf.Serialize(ms, obj);
        return ms.ToArray();
    }
}

이 함수를 코드에 복사하고 바이트 배열로 변환해야하는 객체를 보내면됩니다. 바이트 배열을 다시 객체로 변환해야하는 경우 아래 함수를 사용할 수 있습니다.

// Convert a byte array to an Object
public static Object ByteArrayToObject(byte[] arrBytes)
{
    using (var memStream = new MemoryStream())
    {
        var binForm = new BinaryFormatter();
        memStream.Write(arrBytes, 0, arrBytes.Length);
        memStream.Seek(0, SeekOrigin.Begin);
        var obj = binForm.Deserialize(memStream);
        return obj;
    }
}

이러한 함수는 사용자 정의 클래스와 함께 사용할 수 있습니다. [Serializable]직렬화를 활성화하려면 클래스에 속성을 추가하기 만하면됩니다.


9
나는 이것을 시도했고 모든 종류의 메타 데이터를 추가했습니다. OP는 메타 데이터를 원하지 않는다고 말했습니다.
user316117 2013-01-23

4
말할 것도없이, 모든 사람들은 당신이 직렬화하려는 것이 당신이 작성한 것이거나 이미 직렬화되도록 미리 설정되어 있다고 가정하는 것 같습니다.
Hexum064

3
MemoryStream두 번째 코드 예제에서 바이트 배열을의 생성자에 직접 전달할 수 있습니다 . 이것은 Write(...)및 의 사용을 제거합니다 Seek(...).
unknown6656

41

직렬화 된 데이터를 정말 압축하려면 직렬화 메서드를 직접 작성할 수 있습니다. 그렇게하면 최소한의 오버 헤드를 갖게됩니다.

예:

public class MyClass {

   public int Id { get; set; }
   public string Name { get; set; }

   public byte[] Serialize() {
      using (MemoryStream m = new MemoryStream()) {
         using (BinaryWriter writer = new BinaryWriter(m)) {
            writer.Write(Id);
            writer.Write(Name);
         }
         return m.ToArray();
      }
   }

   public static MyClass Desserialize(byte[] data) {
      MyClass result = new MyClass();
      using (MemoryStream m = new MemoryStream(data)) {
         using (BinaryReader reader = new BinaryReader(m)) {
            result.Id = reader.ReadInt32();
            result.Name = reader.ReadString();
         }
      }
      return result;
   }

}

작성할 정수가 여러 개이고 문자열이 여러 개 있습니까?
Smith

1
@Smith : 네, 그렇게 할 수 있습니다. 이 BinaryWriter(가)하는 형식으로 기록 할 BinaryReader만큼 당신이 작성하고 같은 순서로 읽을 때, 읽을 수 있습니다.
Guffa

1
BinaryWriter/Reader사용과 차이점은 무엇입니까BinaryFormatter
Smith

3
@Smith : 사용 BinaryWriter/Reader하여 직렬화 / 역 직렬화를 직접 수행하고 절대적으로 필요한 데이터 만 가능한 한 압축하여 쓰고 / 읽을 수 있습니다. BinaryFormatter사용 반사 읽기 / 쓰기 어떤 데이터를 발견하고, 형식을 사용하는 모든 가능한 경우에 작동합니다. 또한 스트림의 형식에 대한 메타 정보를 포함하므로 더 많은 오버 헤드가 추가됩니다.
Guffa

1
@Smith : 열거 형을 int(또는 열거 형의 저장소로 다른 유형을 지정한 경우) 캐스트 하고 쓸 수 있습니다. 읽을 때 열거 형으로 캐스팅 할 수 있습니다.
Guffa

31

우물에서 캐스트 myObject로는 byte[]당신이 경우 또는 명시 적 변환을 가지고하지 않는 한 결코 작업에가는되지 myObject 이다byte[] . 당신의 직렬화 프레임 워크가 필요 어떤 종류. 나에게 가깝고 사랑하는 프로토콜 버퍼 를 포함하여 많은 것이 있습니다 . 공간과 시간 측면에서 꽤 "가볍고 비열하다".

거의 모든 직렬화 프레임 워크에는 직렬화 할 수있는 항목에 대한 상당한 제한이 있습니다. 그러나 크로스 플랫폼이기 때문에 프로토콜 버퍼가 일부 이상입니다.

더 많은 요구 사항을 제공 할 수 있다면 더 많은 도움을 드릴 수 있습니다.하지만 캐스팅만큼 간단하지는 않습니다.

편집 : 이에 응답하기 위해 :

객체의 바이트를 포함하려면 바이너리 파일이 필요합니다. 바이트 만, 메타 데이터는 전혀 없습니다. 패킹 된 객체 대 객체. 그래서 커스텀 직렬화를 구현할 것입니다.

객체의 바이트는 종종 참조라는 점을 명심하십시오. 따라서 무엇을해야할지 알아 내야합니다.

사용자 지정 직렬화 프레임 워크를 디자인하고 구현하는 것이 생각보다 어렵다는 것을 알게 될 것입니다.

저는 개인적으로 몇 가지 특정 유형에 대해서만이 작업을 수행해야하는 경우 일반 직렬화 프레임 워크를 생각하지 않는 것이 좋습니다. 필요한 모든 유형에서 인스턴스 메서드와 정적 메서드를 구현하기 만하면됩니다.

public void WriteTo(Stream stream)
public static WhateverType ReadFrom(Stream stream)

명심해야 할 한 가지는 상속이 포함되면 모든 것이 더 까다로워집니다. 상속이 없으면 어떤 유형으로 시작하는지 알고 있다면 유형 정보를 포함 할 필요가 없습니다. 물론 버전 관리의 문제도 있습니다. 유형의 다른 버전과의 역방향 및 순방향 호환성에 대해 걱정해야합니까?


"protobuf-csharp-port"(Google 코드) 또는 "dotnet-protobufs"(Git)라고 부르는 것이 더 정확합니까?
Marc Gravell

1
객체의 바이트를 포함하려면 바이너리 파일이 필요합니다. 바이트 만, 메타 데이터는 전혀 없습니다. 패킹 된 객체 대 객체. 그래서 커스텀 직렬화를 구현할 것입니다.
chuckhlogan

6
제로 메타 데이터 의 위험 은 너무 늦기 전에 유연성을 허용하는 방법이 거의 없기 때문에 버전 을 매우 침해한다는 것입니다. 프로토콜 버퍼는 데이터 밀도가 매우 높습니다. 나사를 추가로 돌려야합니까?
Marc Gravell

@Marc : 그리고 정수에 대한 물론, PB는 ... 원시 바이트보다 밀도가 끝나게 수 있습니다
존 소총

16

나는 Crystalonics의 대답을 받아 확장 방법으로 바꿨습니다. 다른 사람이 유용하다고 생각하기를 바랍니다.

public static byte[] SerializeToByteArray(this object obj)
{
    if (obj == null)
    {
        return null;
    }
    var bf = new BinaryFormatter();
    using (var ms = new MemoryStream())
    {
        bf.Serialize(ms, obj);
        return ms.ToArray();
    }
}

public static T Deserialize<T>(this byte[] byteArray) where T : class
{
    if (byteArray == null)
    {
        return null;
    }
    using (var memStream = new MemoryStream())
    {
        var binForm = new BinaryFormatter();
        memStream.Write(byteArray, 0, byteArray.Length);
        memStream.Seek(0, SeekOrigin.Begin);
        var obj = (T)binForm.Deserialize(memStream);
        return obj;
    }
}

1
이것은 정말 유용하고 쉽습니다 !! 감사합니다.
MrHIDEn

13

정말 많은 형태를 취할 수있는 직렬화에 대해 이야기하고 있습니다. 작고 바이너리를 원하기 때문에 프로토콜 버퍼는 실행 가능한 옵션이 될 수 있습니다. 또한 버전 허용 성과 이식성을 제공합니다. 와 달리 BinaryFormatter프로토콜 버퍼 와이어 형식은 모든 유형 메타 데이터를 포함하지 않습니다. 데이터를 식별하기위한 매우 간결한 마커입니다.

.NET에는 몇 가지 구현이 있습니다. 특히

나는 protobuf-net (내가 쓴)이 전형적인 C # 클래스 ( "일반적인"프로토콜 버퍼는 코드 생성 을 요구 하는 경향이있다)에서 더 많은 .NET 관용적 사용을 허용한다고 겸손하게 주장한다 . 예를 들면 :

[ProtoContract]
public class Person {
   [ProtoMember(1)]
   public int Id {get;set;}
   [ProtoMember(2)]
   public string Name {get;set;}
}
....
Person person = new Person { Id = 123, Name = "abc" };
Serializer.Serialize(destStream, person);
...
Person anotherPerson = Serializer.Deserialize<Person>(sourceStream);

1
"간결한 마커"조차도 여전히 메타 데이터입니다. OP가 원하는 것이 무엇인지에 대한 나의 이해는 객체의 데이터에 불과했습니다. 예를 들어, 객체가 2 개의 32 비트 정수를 가진 구조체이면 결과는 8 바이트의 바이트 배열이 될 것으로 예상합니다.
user316117 2013-01-23

@ user316117은 버전 관리에 대한 진정한 고통입니다. 각 접근 방식에는 장점과 단점이 있습니다.
Marc Gravell


Proto * 속성을 사용하지 않는 방법이 있습니까? 사용하려는 엔티티가 타사 라이브러리에 있습니다.
Alex 75

5

이것은 나를 위해 일했습니다.

byte[] bfoo = (byte[])foo;

foo 바이트 배열임을 100 % 확신하는 객체입니다.


2

전체 객체를 바이트 스트림으로 "변환"하는 기술인 Serialization을 살펴보십시오 . 네트워크로 보내거나 파일에 기록한 다음 나중에 다시 개체로 복원 할 수 있습니다.


나는 chuckhlogan이 (Formatter == Serialization)을 명시 적으로 거부했다고 생각합니다.
Henk Holterman

@Henk- 이유 가 무엇인지에 따라 다릅니다 . 그는 추가 정보에 대해 언급했습니다.이 정보를 유형 메타 데이터와 필드 정보로 간주합니다. 오버 헤드없이 직렬화를 사용할 수 있습니다. 그냥와 BinaryFormatter.
Marc Gravell

2

객체를 byte []로 변환하는 또 다른 방법을 찾았습니다. 여기 내 솔루션이 있습니다.

IEnumerable en = (IEnumerable) myObject;
byte[] myBytes = en.OfType<byte>().ToArray();

문안 인사


1

객체의 메모리에 직접 액세스하려면 ( "코어 덤프"를 수행하기 위해) 안전하지 않은 코드로 이동해야합니다.

BinaryWriter보다 더 간결한 것을 원하거나 원시 메모리 덤프가 제공하는 경우 개체에서 중요한 정보를 추출하여 최적의 방식으로 압축하는 사용자 지정 직렬화 코드를 작성해야합니다.

PS 편집 BinaryWriter 접근 방식을 DeflateStream으로 래핑하여 데이터를 압축하는 것은 매우 쉽습니다. 이는 일반적으로 데이터 크기를 대략 절반으로 줄입니다.


1
안전하지 않은 코드로는 충분하지 않습니다. C # 및 CLR을 사용하면 안전하지 않은 코드에서도 관리되는 개체에 대한 원시 포인터를 가져 오거나 두 개의 개체 참조를 공용체에 넣을 수 없습니다.
Pavel Minaev

0

나는 당신이하려는 것이 불가능하다고 믿습니다. 생성

된 정크는 BinaryFormatter프로그램이 중지 된 후 파일에서 개체를 복구하는 데 필요합니다.
그러나 객체 데이터를 얻을 수 있습니다. 정확한 크기 만 알면됩니다 (소리보다 어렵습니다).

public static unsafe byte[] Binarize(object obj, int size)
{
    var r = new byte[size];
    var rf = __makeref(obj);
    var a = **(IntPtr**)(&rf);
    Marshal.Copy(a, r, 0, size);
    return res;
}

다음을 통해 복구 할 수 있습니다.

public unsafe static dynamic ToObject(byte[] bytes)
{
    var rf = __makeref(bytes);
    **(int**)(&rf) += 8;
    return GCHandle.Alloc(bytes).Target;
}

위의 메서드가 직렬화에 작동하지 않는 이유는 반환 된 데이터의 처음 4 바이트가 RuntimeTypeHandle. 는 RuntimeTypeHandle개체의 레이아웃 / 유형을 설명하지만, 그것의 값은 프로그램이 실행 된 때마다 변경됩니다.

편집 : 그건 어리석은 짓하지 마세요-> 이미 deserialize 할 객체의 유형을 알고 있다면 deserialization시에 해당 바이트를 전환 할 수 있습니다 BitConvertes.GetBytes((int)typeof(yourtype).TypeHandle.Value).

당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.