Search code examples
c#.netdatacontractserializer

Exclude lambda (or delegates) in DataContract


I have a scenario where I will receive a dictionary<string, object>(); that can also be recursive though not always, but the main issue is that it contains lambdas.

as we implement a transaction system I need to clone it, which works fine until I hit the lambdas. I tried to move these to delegates but the error changes and still gives me runtime issue.

technically I can re inject the lambdas after the cloning, but don't know how to tell the DataContractSerializer to ignore these.

I also cannot remove the lambdas prior to the cloning as the original object still needs them if I cancel the transaction.


Solution

  • In your Clone() method you can use the data contract surrogate functionality to replace all System.Delegate objects in your serialization graph with a serializable type, such as a lookup in a dictionary of delegates that you build as you serialize. Then, as you deserialize, you could replace the deserialized serialization surrogates with the original delegates.

    The following does this:

    public static class DataContractExtensions
    {
        public static Dictionary<string, object> Clone(this Dictionary<string, object> dictionary)
        {
            if (dictionary == null)
                return null;
    
            var surrogate = new DelegateSurrogateSelector();
    
            var types = new[] 
                {
                    typeof(Dictionary<string, object>),
                    typeof(DelegateSurrogateId),
                    // Add in whatever additional known types you want.
                };
    
            var serializer = new DataContractSerializer(
                typeof(Dictionary<string, object>),
                types, int.MaxValue, false, false,
                surrogate);
    
            var xml = dictionary.ToContractXml(serializer, null);
    
            return FromContractXml<Dictionary<string, object>>(xml, serializer);
        }
    
        public static string ToContractXml<T>(this T obj, DataContractSerializer serializer, XmlWriterSettings settings)
        {
            serializer = serializer ?? new DataContractSerializer(obj == null ? typeof(T) : obj.GetType());
            using (var textWriter = new StringWriter())
            {
                using (var xmlWriter = XmlWriter.Create(textWriter, settings))
                {
                    serializer.WriteObject(xmlWriter, obj);
                }
                return textWriter.ToString();
            }
        }
    
        public static T FromContractXml<T>(string xml, DataContractSerializer serializer)
        {
            using (var textReader = new StringReader(xml ?? ""))
            using (var xmlReader = XmlReader.Create(textReader))
            {
                return (T)(serializer ?? new DataContractSerializer(typeof(T))).ReadObject(xmlReader);
            }
        }
    
        [DataContract]
        class DelegateSurrogateId
        {
            [DataMember]
            public int Id { get; set; }
        }
    
        class DelegateSurrogateSelector : IDataContractSurrogate
        {
            public Dictionary<int, System.Delegate> DelegateDictionary { get; private set; }
    
            public DelegateSurrogateSelector()
            {
                this.DelegateDictionary = new Dictionary<int, Delegate>();
            }
    
            #region IDataContractSurrogate Members
    
            public object GetCustomDataToExport(Type clrType, Type dataContractType)
            {
                throw new NotImplementedException();
            }
    
            public object GetCustomDataToExport(MemberInfo memberInfo, Type dataContractType)
            {
                throw new NotImplementedException();
            }
    
            public Type GetDataContractType(Type type)
            {
                if (typeof(Delegate).IsAssignableFrom(type))
                    return typeof(DelegateSurrogateId);
                return type;
            }
    
            public object GetDeserializedObject(object obj, Type targetType)
            {
                var id = obj as DelegateSurrogateId;
                if (id != null)
                    return DelegateDictionary[id.Id];
                return obj;
            }
    
            public void GetKnownCustomDataTypes(Collection<Type> customDataTypes)
            {
                throw new NotImplementedException();
            }
    
            public object GetObjectToSerialize(object obj, Type targetType)
            {
                var del = obj as Delegate;
                if (del != null)
                {
                    var id = DelegateDictionary.Count;
                    DelegateDictionary.Add(id, del);
                    return new DelegateSurrogateId { Id = id };
                }
                return obj;
            }
    
            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();
            }
    
            #endregion
        }
    }
    

    Using the above Clone() extension method, the following test will pass:

    Func<bool> returnTrue = () => true;
    Func<bool> returnFalse = () => false;
    
    var dictionary = new Dictionary<string, object>()
    {
        { "a", "hello"},
        { "b", 10101 },
        { "c", returnTrue },
        { "d", new Dictionary<string, object>()
            {
                { "q", 101 },
                { "r", returnFalse },
            }
        }
    };
    
    var dictionary2 = dictionary.Clone();
    
    Assert.AreEqual(returnTrue, dictionary2["c"]); // No failure
    var inner = (Dictionary<string, object>)dictionary["d"];
    var inner2 = (Dictionary<string, object>)dictionary2["d"];
    
    Assert.AreEqual(inner["r"], inner2["r"]);    // No failure
    

    Notes: