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.
CodePudding user response:
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:
- Data contract serialization surrogates work differently in .NET Core. There, the
IDataContractSurrogate
interface has been replaced withISerializationSurrogateProvider
.