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

C# 如何在c中序列化/反序列化不可变列表类型#,c#,.net,serialization,immutability,datacontractserializer,C#,.net,Serialization,Immutability,Datacontractserializer,如果我定义了一个类 [DataContract()] class MyObject { [DataMember()] ImmutableList<string> Strings { get; private set} } 我是否可以向.NET序列化机制添加一些自定义支持以支持此类型并向其演示如何反序列化它 目前,该集合在反序列化时被跳过,尽管序列化它很好。Heh;我可以想象这里发生了什么。。。生成的代码可能正在执行(解释): 问题是BCL不可变API将要求您每次捕获

如果我定义了一个类

[DataContract()]
class MyObject {
    [DataMember()]
    ImmutableList<string> Strings { get; private set}
}
我是否可以向.NET序列化机制添加一些自定义支持以支持此类型并向其演示如何反序列化它


目前,该集合在反序列化时被跳过,尽管序列化它很好。

Heh;我可以想象这里发生了什么。。。生成的代码可能正在执行(解释):

问题是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
有许多不同的实现,其中一些实现返回非无效的结果,需要忽略这些结果

缺少非公共构造函数也可能会让它有点不安,但如果这是主要问题,我希望在它尝试创建非空列表时会出现异常

当然,就性能而言,
list=list.Add(…)
API可能不是最适合使用的API(尽管它应该可以工作)

我最近就这个话题写了一篇博客(在protobuf net的上下文中,protobuf net现在已经被更新以处理这些集合类型):希望这篇博客文章能够解释为什么这些差异意味着它不能很好地处理现有的序列化技术,以及如何更新序列化库以适应此场景


要直接回答这个问题,我想说答案很简单:因为还没有对
DataContractSerializer
进行支持不可变集合所需的更改。我不知道是否有解决这一问题的计划。但是:我高兴地宣布:“在protobuf网络中工作”;p

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

[DataContract()]
class MyObject {

    public ImmutableList<string> Strings { get; private set}

    [DataMember(Name="Strings")]
    private List<String> _Strings;

    [OnSerializing()]
    public void OnSerializing(StreamingContext ctxt){
        _Strings = Strings.ToList();
    }

    [OnDeserialized()]
    public void OnDeserialized(StreamingContext ctxt){
        Strings = ImmutableList<string>.Empty.AddRange(_Strings);
    }
}
[DataContract()]
类MyObject{
公共不可变列表字符串{get;private set}
[DataMember(Name=“Strings”)]
私有列表_字符串;
[正在序列化()]
正在序列化的公共void(StreamingContext ctxt){
_Strings=Strings.ToList();
}
[OnDeserialized()]
已序列化的公共void(StreamingContext ctxt){
Strings=ImmutableList.Empty.AddRange(_Strings);
}
}
这并不是非常漂亮,但正如Marc Gravell在他的回答中所指出的,DataContract序列化程序在不可变集合方面是不可靠的,没有简单的钩子可以教会它如何在没有上述类型的攻击的情况下运行

更新

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<Node> Nodes;

        public FixtureType(){
            Nodes = ImmutableList<Node>.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<T>(Stream data)
        {
            DataContractSerializer ser = new DataContractSerializer
                  ( typeof(T)
                  , new DataContractSerializerSettings() { DataContractSurrogate = new ImmutableSurrogateSerializer() }
                  );
            return (T)ser.ReadObject(data);
        }

        public static T Load<T>(string data)
        {
            using (var stream = new MemoryStream(Encoding.UTF8.GetBytes(data)))
            {
                return Load<T>(stream);
            }
        }

        [Fact]
        public void ShouldWork()
        {
            var o = new FixtureType();

            var s = ToXML(o);

            var oo = Load<FixtureType>(s);

            oo.Nodes.Count().Should().Be(3);
            var names = oo.Nodes.Select(n => n.Name).ToList();
            names.ShouldAllBeEquivalentTo(new[]{"A", "B", "C"});

        }

    }
}
DataContract序列化程序未断开。有一种方法可以吸引代理。请参阅此单独的答案,其中显示了另一种技术


还有另一种通过接口实现的干净方法。DataContractSerializer允许您为不可序列化的对象提供代理。下面是
ImmutableList
的示例和测试用例。它使用反射,可能比我聪明的人可以对其进行优化,但它就在这里

测试用例

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<Node> Nodes;

        public FixtureType(){
            Nodes = ImmutableList<Node>.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<T>(Stream data)
        {
            DataContractSerializer ser = new DataContractSerializer
                  ( typeof(T)
                  , new DataContractSerializerSettings() { DataContractSurrogate = new ImmutableSurrogateSerializer() }
                  );
            return (T)ser.ReadObject(data);
        }

        public static T Load<T>(string data)
        {
            using (var stream = new MemoryStream(Encoding.UTF8.GetBytes(data)))
            {
                return Load<T>(stream);
            }
        }

        [Fact]
        public void ShouldWork()
        {
            var o = new FixtureType();

            var s = ToXML(o);

            var oo = Load<FixtureType>(s);

            oo.Nodes.Count().Should().Be(3);
            var names = oo.Nodes.Select(n => n.Name).ToList();
            names.ShouldAllBeEquivalentTo(new[]{"A", "B", "C"});

        }

    }
}
使用FluentAssertions;
使用System.Collections.Immutable;
使用System.IO;
使用System.Linq;
使用System.Runtime.Serialization;
使用系统文本;
使用System.Xml;
使用Xunit;
命名空间ReactiveUI.Ext.Spec
{
[DataContract(Name=“Node”,命名空间=”http://foo.com/")]
类节点
{
[DataMember()]
公共字符串名称;
}
[DataContract(Name=“Fixture”,命名空间=”http://foo.com/")]
类FixtureType
{
[DataMember()]
公共不可变列表节点;
公共FixtureType(){
Nodes=ImmutableList.Empty.AddRange(新[]
{new Node(){Name=“A”}
,新节点(){Name=“B”}
,新节点(){Name=“C”}
});
}
}
公共类ImmutableUrrogateSpect
{  
公共静态字符串ToXML(对象obj)
{
var settings=newxmlwritersettings{Indent=true};
使用(MemoryStream MemoryStream=new MemoryStream())
使用(StreamReader=新StreamReader(memoryStream))
使用(XmlWriter=XmlWriter.Create(内存流,设置))
{
DataContractSerializer序列化程序=
新的DataContractSerializer
(obj.GetType()
,new DataContractSerializerSettings(){DataContractSurrogate=new ImmutableUrrogateSerializer()}
);
serializer.WriteObject(writer,obj);
writer.Flush();
memoryStream.Position=0;
返回reader.ReadToEnd();
}
}
公共静态T负载(流数据)
{
DataContractSerializer ser=新的DataContractSerializer
(T型)
,new DataContractSerializerSettings(){DataContractSurrogate=new ImmutableUrrogateSerializer()}
);
返回(T)序列读取对象(数据);
}
公共静态T负载(字符串数据)
{
使用(var stream=newmemoryStream(Encoding.UTF8.GetBytes(data)))
{
返回负载(流);
}
}
[事实]
公共空间应该工作
{
var o=新的FixtureType();
var s=毒性物质(o);
var oo=负载;
oo.Nodes.Count()应()为(3);
var Name=oo.Nodes.Select(n=>n.Name.ToList();
名称。应全部等效于(新[]{“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<T>
    {
        public static ImmutableList<T> ToImmutable( List<T> list )
        {
            return ImmutableList<T>.Empty.AddRange(list);
        }

        public static List<T> ToList(ImmutableList<T> list){
            return list.ToList();
        }

        public static object ToImmutable( object list )
        {
            return ToImmutable(( List<T> ) list);
        }

        public static object ToList(object list){
            return ToList(( ImmutableList<T> ) list);
        }

    }

    static class ImmutableListListConverter {


        static ConcurrentDictionary<Tuple<string, Type>, Func<object,object>> _MethodCache 
            = new ConcurrentDictionary<Tuple<string, Type>, Func<object,object>>();

        public static Func<object,object> 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<object,object> ToImmutableMethod( Type targetType )
        {
            return ImmutableListListConverter.CreateMethod("ToImmutable", targetType.GenericTypeArguments[0]);
        }

        public static Func<object,object> ToListMethod( Type targetType )
        {
            return ImmutableListListConverter.CreateMethod("ToList", targetType.GenericTypeArguments[0]);
        }

        private static Func<object,object> MakeLambda(this MethodInfo method )
        {
            return (Func<object,object>) method.CreateDelegate(Expression.GetDelegateType(
            (from parameter in method.GetParameters() select parameter.ParameterType)
            .Concat(new[] { method.ReturnType })
            .ToArray()));
        }

    }

    public class ImmutableSurrogateSerializer : IDataContractSurrogate
    {
        static ConcurrentDictionary<Type, Type> _TypeCache = new ConcurrentDictionary<Type, Type>();

        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<Type> 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() { }
    }
}
使用系统;
使用System.Collections.Concurrent;
使用System.Collections.Generic;
使用System.Collections.Immutable;
使用System.Linq;
使用System.Linq.Expressions;
运用系统反思;
使用System.Runtime.Serialization;
命名空间ReactiveUI.Ext
{
类ImmutableListConver
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<T>
    {
        public static ImmutableList<T> ToImmutable( List<T> list )
        {
            return ImmutableList<T>.Empty.AddRange(list);
        }

        public static List<T> ToList(ImmutableList<T> list){
            return list.ToList();
        }

        public static object ToImmutable( object list )
        {
            return ToImmutable(( List<T> ) list);
        }

        public static object ToList(object list){
            return ToList(( ImmutableList<T> ) list);
        }

    }

    static class ImmutableListListConverter {


        static ConcurrentDictionary<Tuple<string, Type>, Func<object,object>> _MethodCache 
            = new ConcurrentDictionary<Tuple<string, Type>, Func<object,object>>();

        public static Func<object,object> 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<object,object> ToImmutableMethod( Type targetType )
        {
            return ImmutableListListConverter.CreateMethod("ToImmutable", targetType.GenericTypeArguments[0]);
        }

        public static Func<object,object> ToListMethod( Type targetType )
        {
            return ImmutableListListConverter.CreateMethod("ToList", targetType.GenericTypeArguments[0]);
        }

        private static Func<object,object> MakeLambda(this MethodInfo method )
        {
            return (Func<object,object>) method.CreateDelegate(Expression.GetDelegateType(
            (from parameter in method.GetParameters() select parameter.ParameterType)
            .Concat(new[] { method.ReturnType })
            .ToArray()));
        }

    }

    public class ImmutableSurrogateSerializer : IDataContractSurrogate
    {
        static ConcurrentDictionary<Type, Type> _TypeCache = new ConcurrentDictionary<Type, Type>();

        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<Type> 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() { }
    }
}