假设我有一个像这样的不可变值类型:
[Serializable]
[DataContract]
public struct MyValueType : ISerializable
{
private readonly int _x;
private readonly int _z;
public MyValueType(int x, int z)
: this()
{
_x = x;
_z = z;
}
// this constructor is used for deserialization
public MyValueType(SerializationInfo info, StreamingContext text)
: this()
{
_x = info.GetInt32("X");
_z = info.GetInt32("Z");
}
[DataMember(Order = 1)]
public int X
{
get { return _x; }
}
[DataMember(Order = 2)]
public int Z
{
get { return _z; }
}
public static bool operator ==(MyValueType a, MyValueType b)
{
return a.Equals(b);
}
public static bool operator !=(MyValueType a, MyValueType b)
{
return !(a == b);
}
public override bool Equals(object other)
{
if (!(other is MyValueType))
{
return false;
}
return Equals((MyValueType)other);
}
public bool Equals(MyValueType other)
{
return X == other.X && Z == other.Z;
}
public override int GetHashCode()
{
unchecked
{
return (X * 397) ^ Z;
}
}
// this method is called during serialization
public void GetObjectData(SerializationInfo info, StreamingContext context)
{
info.AddValue("X", X);
info.AddValue("Z", Z);
}
public override string ToString()
{
return string.Format("[{0}, {1}]", X, Z);
}
}
它适用于BinaryFormatter或DataContractSerializer,但当我尝试将它与protobuf-net(http://code.google.com/p/protobuf-net/)序列化程序一起使用时,我收到此错误:
无法对属性应用更改 ConsoleApplication.Program + MyValueType.X
如果我将setter应用于标有DataMember属性的属性,它将起作用,但它会打破这种值类型的不变性,这对我们来说是不可取的。
有谁知道我需要做些什么才能让它发挥作用?我注意到ProtoBu.Serializer.Serialize方法有一个重载,它接受了一个SerializationInfo和一个StreamingContext但我没有在实现ISerializable接口的上下文之外使用它们,所以任何关于如何使用它们的代码示例这个背景将非常感谢!
谢谢,
编辑:所以我挖了一些旧的MSDN文章,并更好地了解了SerializationInfo和StreamingContext的使用位置和方式,但是当我尝试这样做时:var serializationInfo = new SerializationInfo(
typeof(MyValueType), new FormatterConverter());
ProtoBuf.Serializer.Serialize(serializationInfo, valueType);
事实证明Serialize<T>
方法只允许引用类型,有特殊原因吗?考虑到我能够序列化通过引用类型公开的值类型,这似乎有点奇怪。
答案 0 :(得分:11)
您使用的是哪个版本的protobuf-net?如果你是最新的v2版本,它应该自动处理。如果我尚未部署此代码,我会立即更新下载区域,但基本上如果您的类型是未加修饰的(没有属性),它将检测常见的&#34;元组&#34 ;你正在使用的模式,并决定(从构造函数)x
(构造函数参数)/ X
(属性)是字段1,z
/ Z
是字段2
另一种方法是标记字段:
[ProtoMember(1)]
private readonly int _x;
[ProtoMember(2)]
private readonly int _z;
(或者字段上的[DataMember(Order=n)]
)
哪个应该有效,具体取决于信任等级。我尚未完成的工作是将构造函数代码概括为归因方案。这并不难,但我想首先推动基本情况,然后进化。
我已添加以下两个样本/测试with full code here:
[Test]
public void RoundTripImmutableTypeAsTuple()
{
using(var ms = new MemoryStream())
{
var val = new MyValueTypeAsTuple(123, 456);
Serializer.Serialize(ms, val);
ms.Position = 0;
var clone = Serializer.Deserialize<MyValueTypeAsTuple>(ms);
Assert.AreEqual(123, clone.X);
Assert.AreEqual(456, clone.Z);
}
}
[Test]
public void RoundTripImmutableTypeViaFields()
{
using (var ms = new MemoryStream())
{
var val = new MyValueTypeViaFields(123, 456);
Serializer.Serialize(ms, val);
ms.Position = 0;
var clone = Serializer.Deserialize<MyValueTypeViaFields>(ms);
Assert.AreEqual(123, clone.X);
Assert.AreEqual(456, clone.Z);
}
}
此外:
事实证明,Serialize方法只允许引用类型
是的,这是与拳击模型等相关的v1的设计限制;这不再适用于v2。
另请注意,protobuf-net并不本身消耗ISerializable
(尽管它可用于实施 ISerializable
)
答案 1 :(得分:1)
由于链接断开并且我看不到MyValueTypeViaFields
代码,因此所选答案对我不起作用。
在任何情况下,我的班级都有No parameterless constructor found
例外:
[ProtoContract]
public class FakeSimpleEvent
: IPersistableEvent
{
[ProtoMember(1)]
public Guid AggregateId { get; }
[ProtoMember(2)]
public string Value { get; }
public FakeSimpleEvent(Guid aggregateId, string value)
{
AggregateId = aggregateId;
Value = value;
}
}
使用以下代码反序列化时:
public class BinarySerializationService
: IBinarySerializationService
{
public byte[] ToBytes(object obj)
{
if (obj == null) throw new ArgumentNullException(nameof(obj));
using (var memoryStream = new MemoryStream())
{
Serializer.Serialize(memoryStream, obj);
var bytes = memoryStream.ToArray();
return bytes;
}
}
public TType FromBytes<TType>(byte[] bytes)
where TType : class
{
if (bytes == null) throw new ArgumentNullException(nameof(bytes));
var type = typeof(TType);
var result = FromBytes(bytes, type);
return (TType)result;
}
public object FromBytes(byte[] bytes, Type type)
{
if (bytes == null) throw new ArgumentNullException(nameof(bytes));
int length = bytes.Length;
using (var memoryStream = new MemoryStream())
{
memoryStream.Write(bytes, 0, length);
memoryStream.Seek(0, SeekOrigin.Begin);
var obj = Serializer.Deserialize(type, memoryStream);
return obj;
}
}
}
被称为var dataObject = (IPersistableEvent)_binarySerializationService.FromBytes(data, eventType);
我的消息类FakeSimpleEvent
确实具有无参数的构造函数,因为我希望它是不可变的。
我正在使用protobuf-net 2.4.0
,并且可以确认它支持复杂的构造函数和不可变的消息类。只需使用以下装饰器
[ProtoContract(SkipConstructor = true)]
如果为true,则在以下过程中绕过该类型的构造函数 反序列化,表示任何字段初始化程序或其他 初始化代码被跳过。
更新1:(2019年6月20日) 我不喜欢使用属于protobuffer的属性来污染我的类,因为域模型应该与技术无关(当然不是dotnet框架的类型)
因此,要将protobuf-net与没有属性且没有无参数构造函数(即不可变的)的消息类一起使用,您可以具有以下内容:
public class FakeSimpleEvent
: IPersistableEvent
{
public Guid AggregateId { get; }
public string Value { get; }
public FakeSimpleEvent(Guid aggregateId, string value)
{
AggregateId = aggregateId;
Value = value;
}
}
,然后为此类配置protobuf。
var fakeSimpleEvent = RuntimeTypeModel.Default.Add(typeof(FakeSimpleEvent), false);
fakeSimpleEvent.Add(1, nameof(FakeSimpleEvent.AggregateId));
fakeSimpleEvent.Add(2, nameof(FakeSimpleEvent.Value));
fakeSimpleEvent.UseConstructor = false;
这与我先前的回答相同,但更加干净。
PS:不要介意IPersistableEvent
。与示例无关紧要,只是我在其他地方使用的标记界面