如何在c#中序列化/反序列化不可变列表类型

如果我定义了一个类

[DataContract()] class MyObject { [DataMember()] ImmutableList Strings { get; private set} } 

ImmutableList类型来自immutables库https://www.nuget.org/packages/Microsoft.Bcl.Immutable 。 请注意,类ImmutableList没有默认构造函数或可变的Add方法。 将内容添加到列表中即可获取表单。

 myList = myList.Add("new string"); 

我是否可以为.NET序列化机制添加一些自定义支持以支持此类型并向其显示如何对其进行反序列化?

目前,在反序列化时只会跳过该集合,尽管可以将其序列化。

通过IDataContractSurrogate接口还有另一种干净的方法。 DataContractSerializer允许您为非可序列化对象提供代理。 下面是ImmutableList的示例和测试用例。 它使用reflection,可能可以由比我更聪明的人优化,但在这里。

测试用例

 using FluentAssertions; using System.Collections.Immutable; using System.IO; using System.Linq; using System.Runtime.Serialization; using System.Text; using System.Xml; using Xunit; namespace ReactiveUI.Ext.Spec { [DataContract(Name="Node", Namespace="http://foo.com/")] class Node { [DataMember()] public string Name; } [DataContract(Name="Fixture", Namespace="http://foo.com/")] class FixtureType { [DataMember()] public ImmutableList Nodes; public FixtureType(){ Nodes = ImmutableList.Empty.AddRange( new [] { new Node(){Name="A"} , new Node(){Name="B"} , new Node(){Name="C"} }); } } public class ImmutableSurrogateSpec { public static string ToXML(object obj) { var settings = new XmlWriterSettings { Indent = true }; using (MemoryStream memoryStream = new MemoryStream()) using (StreamReader reader = new StreamReader(memoryStream)) using (XmlWriter writer = XmlWriter.Create(memoryStream, settings)) { DataContractSerializer serializer = new DataContractSerializer ( obj.GetType() , new DataContractSerializerSettings() { DataContractSurrogate = new ImmutableSurrogateSerializer() } ); serializer.WriteObject(writer, obj); writer.Flush(); memoryStream.Position = 0; return reader.ReadToEnd(); } } public static T Load(Stream data) { DataContractSerializer ser = new DataContractSerializer ( typeof(T) , new DataContractSerializerSettings() { DataContractSurrogate = new ImmutableSurrogateSerializer() } ); return (T)ser.ReadObject(data); } public static T Load(string data) { using (var stream = new MemoryStream(Encoding.UTF8.GetBytes(data))) { return Load(stream); } } [Fact] public void ShouldWork() { var o = new FixtureType(); var s = ToXML(o); var oo = Load(s); oo.Nodes.Count().Should().Be(3); var names = oo.Nodes.Select(n => n.Name).ToList(); names.ShouldAllBeEquivalentTo(new[]{"A", "B", "C"}); } } } 

实施

 using System; using System.Collections.Concurrent; using System.Collections.Generic; using System.Collections.Immutable; using System.Linq; using System.Linq.Expressions; using System.Reflection; using System.Runtime.Serialization; namespace ReactiveUI.Ext { class ImmutableListListConverter { public static ImmutableList ToImmutable( List list ) { return ImmutableList.Empty.AddRange(list); } public static List ToList(ImmutableList list){ return list.ToList(); } public static object ToImmutable( object list ) { return ToImmutable(( List ) list); } public static object ToList(object list){ return ToList(( ImmutableList ) list); } } static class ImmutableListListConverter { static ConcurrentDictionary, Func> _MethodCache = new ConcurrentDictionary, Func>(); public static Func CreateMethod( string name, Type genericType ) { var key = Tuple.Create(name, genericType); if ( !_MethodCache.ContainsKey(key) ) { _MethodCache[key] = typeof(ImmutableListListConverter<>) .MakeGenericType(new []{genericType}) .GetMethod(name, new []{typeof(object)}) .MakeLambda(); } return _MethodCache[key]; } public static Func ToImmutableMethod( Type targetType ) { return ImmutableListListConverter.CreateMethod("ToImmutable", targetType.GenericTypeArguments[0]); } public static Func ToListMethod( Type targetType ) { return ImmutableListListConverter.CreateMethod("ToList", targetType.GenericTypeArguments[0]); } private static Func MakeLambda(this MethodInfo method ) { return (Func) method.CreateDelegate(Expression.GetDelegateType( (from parameter in method.GetParameters() select parameter.ParameterType) .Concat(new[] { method.ReturnType }) .ToArray())); } } public class ImmutableSurrogateSerializer : IDataContractSurrogate { static ConcurrentDictionary _TypeCache = new ConcurrentDictionary(); public Type GetDataContractType( Type targetType ) { if ( _TypeCache.ContainsKey(targetType) ) { return _TypeCache[targetType]; } if(targetType.IsGenericType && targetType.GetGenericTypeDefinition() == typeof(ImmutableList<>)) { return _TypeCache[targetType] = typeof(List<>).MakeGenericType(targetType.GetGenericArguments()); } else { return targetType; } } public object GetDeserializedObject( object obj, Type targetType ) { if ( _TypeCache.ContainsKey(targetType) ) { return ImmutableListListConverter.ToImmutableMethod(targetType)(obj); } return obj; } public object GetObjectToSerialize( object obj, Type targetType ) { if ( targetType.IsGenericType && targetType.GetGenericTypeDefinition() == typeof(ImmutableList<>) ) { return ImmutableListListConverter.ToListMethod(targetType)(obj); } return obj; } public object GetCustomDataToExport( Type clrType, Type dataContractType ) { throw new NotImplementedException(); } public object GetCustomDataToExport( System.Reflection.MemberInfo memberInfo, Type dataContractType ) { throw new NotImplementedException(); } public void GetKnownCustomDataTypes( System.Collections.ObjectModel.Collection customDataTypes ) { throw new NotImplementedException(); } public Type GetReferencedTypeOnImport( string typeName, string typeNamespace, object customData ) { throw new NotImplementedException(); } public System.CodeDom.CodeTypeDeclaration ProcessImportedType( System.CodeDom.CodeTypeDeclaration typeDeclaration, System.CodeDom.CodeCompileUnit compileUnit ) { throw new NotImplementedException(); } public ImmutableSurrogateSerializer() { } } } 

一种方法是使用代理可变列表并使用OnSerializing和OnDeserialized挂钩

 [DataContract()] class MyObject { public ImmutableList Strings { get; private set} [DataMember(Name="Strings")] private List _Strings; [OnSerializing()] public void OnSerializing(StreamingContext ctxt){ _Strings = Strings.ToList(); } [OnDeserialized()] public void OnDeserialized(StreamingContext ctxt){ Strings = ImmutableList.Empty.AddRange(_Strings); } } 

它并不是非常漂亮,但正如Marc Gravell在他的回答中指出的那样, DataContract序列化程序在不可变集合方面受到打破,并且没有简单的钩子来教它如何在没有上述类型的黑客的情况下表现。

UPDATE

DataContract序列化程序没有被破坏。 有一种方法可以将代理人挂钩。请参阅这个单独的答案,显示另一种技术。

https://stackoverflow.com/a/18957739/158285

嘿; 我可以想象这里发生了什么…生成的代码可能正在做(释义):

 var list = obj.Strings; while(CanReadNextItem()) { list.Add(ReadNextItem()); } 

问题是BCL不可变API会要求您每次捕获结果,即

 var list = obj.Strings; while(CanReadNextItem()) { list = list.Add(ReadNextItem()); } obj.Strings = list; // the private set is not a problem for this 

预先存在的列表反序列化代码不能以这种方式工作,因为它从来不需要 – 实际上,有许多不同的Add实现,其中一些返回需要忽略的非void结果。

缺少一个非公共构造函数也可能会使它有点不高兴,但如果这是主要问题 ,我会在尝试创建非空列表时期望exception。

当然,在性能方面, list = list.Add(...) API 可能不是最合适的API(尽管它应该工作)。

我最近在这个主题上发表了博客(在protobuf-net的背景下,现在已经更新以适应这些集合类型): http : //marcgravell.blogspot.co.uk/2013/09/fun-with-immutable- collections.html希望这篇博客文章能够解释为什么这些差异意味着它不能很好地与现有的序列化技术相结合,以及如何更新序列化库以使用这种情况。

要直接回答这个问题,我会说答案很简单: 因为尚未对DataContractSerializer进行必要的更改以支持不可变集合 。 我不知道是否有计划解决这个问题。 但是:我高兴地宣布:“在protobuf-net中工作”; p