How to serialize / deserialize immutable list type in C #

If I have a specific class

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

The ImmutableList<T> comes from the immutables library https://www.nuget.org/packages/Microsoft.Bcl.Immutable . Note that the ImmutableList class does not have a default constructor or Add modifiable method. Adding items to the list takes shape.

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

Can I add some custom support for the .NET serialization engine to support this type and show how to deserialize it?

Currently, the collection is simply skipped for deserialization, although it can be serialized.

+6
source share
3 answers

There is another easy way to do this through the IDataContractSurrogate interface. DataContractSerializer allows you to provide a surrogate for non-serializable objects. The following is an example and test case for ImmutableList<T> . It uses reflection and can probably be optimized by smarter people than me, but here.

Testcase

 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"}); } } } 

IMPLEMENTATION

 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() { } } } 
+6
source

One way to do this is to use a list of modified proxies and use the OnSerializing and OnDeserialized bindings

 [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); } } 

It's not very pretty, but as Mark Gravell noted in his answer, the DataContract serializer is broken up with respect to immutable collections, and there are no simple hooks to teach him how to behave without the aforementioned type of hacking.

UPDATE

The DataContract serializer is not interrupted. There is a way to intercept surrogates. See this separate answer for an alternative technique.

fooobar.com/questions/954398 / ...

+5
source

Heh; I can imagine what is going on here ... the generated code probably does (rephrasing):

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

The problem is that the BCL immutable API will require you to catch the result every time, i.e.

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

The deserialization code of an existing list does not work this way because it never needed to - and indeed, there are many different Add implementations, some of which return non-imaginary results that must be ignored.

The lack of a non-public constructor can also upset him a little, but if that was the main problem, I would prefer an exception when he tries to create a non-zero list.

Of course, in terms of performance, the API list = list.Add(...) is probably not the most suitable for use anyway (although it should work).

I recently wrote about this topic (in the context of protobuf-net, which is now updated to work with these types of collections): http://marcgravell.blogspot.co.uk/2013/09/fun-with-immutable-collections. html Hopefully this blog article should explain why the differences mean that it doesn't work very well with existing serialization methods and how to update serialization libraries to work with this script.

To answer the question directly, I would say that the answer is simple: because the necessary changes to support immutable collections have not yet been made for the DataContractSerializer . I do not know if there is a plan to solve this problem. But: I happily declare: "works in protobuf-net", p

+4
source

Source: https://habr.com/ru/post/954398/


All Articles