Copy two identical object with different namespaces (recursive reflection) - c#

I'm working in c# with several workspaces that have one specific class which his always the same in each workspace.
I would like to be able have a copy of this class to be able to work with it without dealing with namespaces differences.
example :
namespace1 {
class class1{
public class2;
}
class class2{
public string;
}
}
namespace2 {
class class1{
public class2;
}
class class2{
public string;
}
}
In my copied Class I've got a function to copy all data's to one of the namespace's class.
It's working if i only have c# standard types. I got exeption ( "Object does not match target type." ) as soon as I'm dealing with class2 object (which is also from different namespaces)
public Object toNamespaceClass(Object namespaceClass)
{
try
{
Type fromType = this.GetType();
Type toType = namespaceClass.GetType();
PropertyInfo[] fromProps = fromType.GetProperties();
PropertyInfo[] toProps = toType.GetProperties();
for (int i = 0; i < fromProps.Length; i++)
{
PropertyInfo fromProp = fromProps[i];
PropertyInfo toProp = toType.GetProperty(fromProp.Name);
if (toProp != null)
{
toProp.SetValue(this, fromProp.GetValue(namespaceClass, null), null);
}
}
}
catch (Exception ex)
{
}
return namespaceClass;
}
Anyone do have any idea of how to deal with this kind of "recursivity reflection".
I hope eveything is understandable.
Thanks, Bye!
Edit :
I think i got it solved (at least in my mind), I'll try the solution back at work tomorrow. Taking my function out of my class and using it recursively if a property is not a standard type is maybe the solution.

BinaryFormatter does not work in .Net 4.5 as it remembers from what type of class the instance was created. But with JSON format, it does not. JSON serializer is implemented by Microsoft in DataContractJosnSerializer.
This works:
public static T2 DeepClone<T1, T2>(T1 obj)
{
DataContractJsonSerializer serializer = new DataContractJsonSerializer(typeof(T1));
DataContractJsonSerializer deserializer = new DataContractJsonSerializer(typeof(T2));
using (var ms = new MemoryStream())
{
serializer.WriteObject(ms, obj);
ms.Position = 0;
return (T2)deserializer.ReadObject(ms);
}
}
and uses as follows:
var b = DeepClone<A, B>(a);

I had the similar problem. I got to use similar classes but different in terms of namespace only. As a quick solution I performed below steps and it works.
Serialize source class into XML.
In SerializedXML replace source namespace with the target one.
DeSerialize with target type.
I know there is performance overhead with above way but it is quick to implement and error free.

I got it solved , just to let you know how I did it :
This solution is sot perfect because it handle only 1 dimensions array not more.
public static Object CopyObject(Object from , Object to)
{
try
{
Type fromType = from.GetType();
Type toType = to.GetType();
PropertyInfo[] fromProps = fromType.GetProperties();
PropertyInfo[] toProps = toType.GetProperties();
for (int i = 0; i < fromProps.Length; i++)
{
PropertyInfo fromProp = fromProps[i];
PropertyInfo toProp = toType.GetProperty(fromProp.Name);
if (toProp != null)
{
if (toProp.PropertyType.Module.ScopeName != "CommonLanguageRuntimeLibrary")
{
if (!toProp.PropertyType.IsArray)
{
ConstructorInfo ci = toProp.PropertyType.GetConstructor(new Type[0]);
if (ci != null)
{
toProp.SetValue(to, ci.Invoke(null), null);
toProp.SetValue(to, gestionRefelexion.CopyObject(fromProp.GetValue(from, null), toProp.GetValue(to, null)), null);
}
}
else
{
Type typeToArray = toProp.PropertyType.GetElementType();
Array fromArray = fromProp.GetValue(from, null) as Array;
toProp.SetValue(to, copyArray(fromArray, typeToArray), null);
}
}
else
{
toProp.SetValue(to, fromProp.GetValue(from, null), null);
}
}
}
}
catch (Exception ex)
{
}
return to;
}
public static Array copyArray(Array from, Type toType)
{
Array toArray =null;
if (from != null)
{
toArray= Array.CreateInstance(toType, from.Length);
for (int i = 0; i < from.Length; i++)
{
ConstructorInfo ci = toType.GetConstructor(new Type[0]);
if (ci != null)
{
toArray.SetValue(ci.Invoke(null), i);
toArray.SetValue(gestionRefelexion.CopyObject(from.GetValue(i), toArray.GetValue(i)), i);
}
}
}
return toArray;
}
Hope this can help some people.
Thanks for helping everyone.
Cheers

public static T DeepClone<T>(T obj)
{
using (var ms = new MemoryStream())
{
var formatter = new BinaryFormatter();
formatter.Serialize(ms, obj);
ms.Position = 0;
return (T) formatter.Deserialize(ms);
}
}
from here

Two identical or similar objects from different namespaces ?
You have this:
namespace Cars
{
public class car {
public string Name;
public void Start() { ... }
}
}
namespace Planes
{
public class plane {
public string Name;
public void Fly() { ... }
}
}
Time to apply some class inheritance:
namespace Vehicles
{
public class vehicle
{
public string Name;
} // class
} // namespace
using Vehicles;
namespace Cars
{
public class car: vehicle
{
public string Name;
public void Start() { ... }
} // class
} // namespace
using Vehicles;
namespace Planes
{
public class plane: vehicle
{
public void Fly() { ... }
}
}
And to copy, there is a copy method or constructor, but, I prefer a custom one:
namespace Vehicles
{
public class vehicle {
public string Name;
public virtual CopyFrom (vehicle Source)
{
this.Name = Source.Name;
// other fields
}
} // class
} // namespace
Cheers.

You either need to refactor all of your duplicate classes into a single shared class or implement a common interface that all of your various classes implement. If you really can't modify the underlying types, create a subclass for each that implements your common interface.
Yes, you can do it with reflection... but you really shouldn't because you end up with brittle, error prone, code.

This problem can be elegantly solves using Protocol Buffers because Protocol Buffers do not hold any metadata about the type they serialize. Two classes with identical fields & properties serialize to the exact same bits.
Here's a little function that will change from O the original type to C the copy type
static public C DeepCopyChangingNamespace<O,C>(O original)
{
using (MemoryStream ms = new MemoryStream())
{
Serializer.Serialize(ms, original);
ms.Position = 0;
C c = Serializer.Deserialize<C>(ms);
return c;
}
}
usage would be
namespace1.class1 orig = new namespace1.class1();
namespace2.class1 copy =
DeepCopyChangingNamespace<namespace1.class1, namespace2.class1>(orig);

Related

Is my understanding of PSPropertyAdapter correct?

I am writing a binary PowerShell module that, much like the ActiveDirectory module, will have a number of cmdlets and types that can potentially return more than the default set of properties - and those properties (100+) are dependent on what the user requests. Just like the AD module, I would like to return the type with the properties that were requested so that it is transparent to the user i.e. not one huge class with lots of empty properties that they haven't requested.
I was looking at the documentation for the ActiveDirectory module and I noticed that things like ADUser and ADComputer ultimately are inherited from ADPropertyCollection which I assume has the properties required in its InnerDictionary after whatever searches it does.
This did not really explain how PowerShell presents the AD types flexibly i.e.
(Get-ADUser -Identity some.user).GetType()
(Get-ADUser -Identity some.user -Properties *).GetType()
# Both return a type of ADUser, despite have drastically different amounts of properties.
This was until I looked at the Types ps1xml file for the AD module, which presents something like this:
<Types>
<!-- other types -->
<Type>
<Name>Microsoft.ActiveDirectory.Management.ADUser</Name>
<TypeAdapter>
<TypeName>Microsoft.ActiveDirectory.Management.ADEntityAdapter</TypeName>
</TypeAdapter>
</Type>
</Types>
So I guess the source of the "magic" properties is ADEntityAdapter which is inherited from the abstract class PSPropertyAdapter.
The issue I have is now I am not sure how to implement it and there are not any easily searchable examples of it being implemented. I appreciate its a real edge case. I have had a small attempt at a very rough implementation below, please ignore any code faux pas - I will of course not actually write the code like below. I just wanted to at least try and show I have thought about this.
using System.Collections.Generic;
namespace Sample
{
public class PropertyCollection
{
public string Id { get; set; }
public Dictionary<string, object> Attributes { get; set; }
public PropertyCollection()
{
this.Id = "SampleID";
this.Attributes = new Dictionary<string, object>();
for (var i = 0; i < 10; i++)
{
this.Attributes.Add("KeyAttribute" + i, i);
}
for (var i = 10; i < 100; i++)
{
this.Attributes.Add("OtherAttribute" + i, i);
}
}
}
}
namespace Sample
{
public class ReturnedClass : PropertyCollection
{
public string SomeName { get; set; }
public ReturnedClass() : base() { }
}
}
using System;
using System.Collections.ObjectModel;
using System.Management.Automation;
namespace Sample
{
public class PropertyEntityAdapter : PSPropertyAdapter
{
public override Collection<PSAdaptedProperty> GetProperties(object baseObject)
{
PropertyCollection pc = baseObject as PropertyCollection;
if (pc == null)
{
throw new Exception("Some Exception");
}
Collection<PSAdaptedProperty> collection = new Collection<PSAdaptedProperty>();
foreach (string name in pc.Attributes.Keys)
{
collection.Add(new PSAdaptedProperty(name, null));
}
return collection;
}
public override PSAdaptedProperty GetProperty(object baseObject, string propertyName)
{
PropertyCollection pc = baseObject as PropertyCollection;
if (pc.Attributes.TryGetValue(propertyName, out object pcValue))
{
return new PSAdaptedProperty(propertyName, pcValue);
}
throw new Exception("Prop not found");
}
public override string GetPropertyTypeName(PSAdaptedProperty adaptedProperty)
{
if (adaptedProperty == null)
{
throw new Exception("prop null");
}
PropertyCollection pc = adaptedProperty.BaseObject as PropertyCollection;
if (pc == null)
{
throw new Exception("not found");
}
if (pc.Attributes.TryGetValue(adaptedProperty.Name, out object pcValue))
{
return pcValue.GetType().FullName;
}
return pc.GetType().FullName;
}
public override object GetPropertyValue(PSAdaptedProperty adaptedProperty)
{
if (adaptedProperty?.BaseObject == null || adaptedProperty?.Name == null)
{
throw new Exception("prop null");
}
PropertyCollection pc = adaptedProperty.BaseObject as PropertyCollection;
if (pc == null)
{
throw new Exception("not found");
}
if (pc.Attributes.TryGetValue(adaptedProperty.Name, out object pcValue))
{
return pcValue;
}
return null;
}
public override bool IsGettable(PSAdaptedProperty adaptedProperty)
{
if (adaptedProperty == null)
{
throw new Exception("prop null");
}
PropertyCollection pc = adaptedProperty.BaseObject as PropertyCollection;
if (pc == null)
{
throw new Exception("not found");
}
if (pc.Attributes.ContainsKey(adaptedProperty.Name))
{
return true;
}
return false;
}
public override bool IsSettable(PSAdaptedProperty adaptedProperty)
{
return false;
}
public override void SetPropertyValue(PSAdaptedProperty adaptedProperty, object value)
{
throw new System.NotImplementedException();
}
}
}
** I have purposely not added any code for the Set* methods to reduce the code required here. If the above seems right, then I am sure I know what to do.
Has anyone done this before? Are there any very basic examples out there? Am I on the right path, or am I misunderstanding? Thanks in advance.
If anyone comes comes looking for this and was interested in doing something similar, then my question is actually more or less how it is done. I hope it is of use or interest to others working on binary modules.
One thing I did find were there were some additional properties that were passed through the GetProperty method that seemed to be from the PowerShell engine itself i.e. PSComputerName, so there was no need to throw an error here, just return a new PSAdaptedProperty with a null object.
public override PSAdaptedProperty GetProperty(object baseObject, string propertyName)
{
PropertyCollection pc = baseObject as PropertyCollection;
if (pc.Attributes.TryGetValue(propertyName, out object pcValue))
{
return new PSAdaptedProperty(propertyName, pcValue);
}
return new PSAdaptedProperty(propertyName, null);
}
You can then either load the Types data via your module manifest or in the console using Update-Typedata -PrependPath path_to_file\file_name.
All the properties in the dictionary are returned to give the appearance that they are full properties of the class.

DataContractJsonSerializer ; ISerializable GetObjectData called with .NET but not with mono

Below is a simplified example of what I'm trying to accomplish.
I have a class DoNotSerializeMe which is part of an external library and cannot be serialized.
using System;
namespace CustomJsonSerialization
{
public class DoNotSerializeMe
{
public string WhyAmIHere;
public DoNotSerializeMe(string mystring)
{
Console.WriteLine(" In DoNotSerializeMe constructor.");
WhyAmIHere = "( constructed with " + mystring + " )";
}
}
}
I also have a class SerializeMe which has a member of type DoNotSerializeMe. I can make this class implement ISerializable and get around the issue of DoNotSerializeMe not being serializable by pulling data and calling the constructor.
using System.Runtime.Serialization;
using System.Security.Permissions;
namespace CustomJsonSerialization
{
[System.Serializable]
public class SerializeMe : ISerializable
{
public DoNotSerializeMe SerializeMeThroughISerializable;
public SerializeMe(string mystring)
{
SerializeMeThroughISerializable = new DoNotSerializeMe(mystring);
}
protected SerializeMe(SerializationInfo info, StreamingContext context)
{
System.Console.WriteLine(" In SerializeMe constructor (ISerializable)");
SerializeMeThroughISerializable = new DoNotSerializeMe(info.GetString("SerializeMeThroughISerializable"));
}
[SecurityPermission(SecurityAction.Demand, SerializationFormatter = true)]
public virtual void GetObjectData(SerializationInfo info, StreamingContext context)
{
System.Console.WriteLine(" In SerializeMe.GetObjectData()");
info.AddValue("SerializeMeThroughISerializable",
"( deserialized through getObjectData " +
SerializeMeThroughISerializable.WhyAmIHere + " )");
}
}
}
Below is a short program that serializes and deserializes the object:
using System;
using System.IO;
using System.Runtime.Serialization.Json;
using System.Text;
namespace CustomJsonSerialization
{
public class Program
{
public static void Main(string[] args)
{
SerializeMe serializeme = new SerializeMe("initial");
Console.WriteLine("I created it: {0}", serializeme.SerializeMeThroughISerializable.WhyAmIHere);
Console.WriteLine();
MemoryStream memstream = new MemoryStream();
DataContractJsonSerializer serializer = new DataContractJsonSerializer(typeof(SerializeMe));
serializer.WriteObject(memstream, serializeme);
Console.WriteLine("I serialized it: {0}", serializeme.SerializeMeThroughISerializable.WhyAmIHere);
Console.WriteLine();
Console.WriteLine("Json:");
Console.WriteLine(Encoding.ASCII.GetString(memstream.ToArray()));
Console.WriteLine();
memstream.Seek(0, SeekOrigin.Begin);
SerializeMe anotherSerializeMe = (SerializeMe)serializer.ReadObject(memstream);
Console.WriteLine("I deserialized it: {0}", anotherSerializeMe.SerializeMeThroughISerializable.WhyAmIHere);
}
}
}
When running through .NET (4.5), I get the following:
In DoNotSerializeMe constructor.
I created it: ( constructed with initial )
In SerializeMe.GetObjectData()
I serialized it: ( constructed with initial )
Json:
{"SerializeMeThroughISerializable":"( deserialized through getObjectData ( constructed with initial ) )"}
In SerializeMe constructor (ISerializable)
In DoNotSerializeMe constructor.
I deserialized it: ( constructed with ( deserialized through getObjectData ( constructed with initial ) ) )
The serializer called the ISerializable construction and the GetObjectData when serializing / deserializing (as expected). I never serialize or deserialize the DoNotSerializeMe object directly.
However, when running the same build through mono (tried 3.10.0 and 4.0.2), I get the following:
In DoNotSerializeMe constructor.
I created it: ( constructed with initial )
I serialized it: ( constructed with initial )
Json:
{"SerializeMeThroughISerializable":{"WhyAmIHere":"( constructed with initial )"}}
I deserialized it: ( constructed with initial )
Obviously, if DoNotSerializeMe was truly not serializable, this would lead to an error.
Is there an elegant way to get around this without using Json.NET? I'm not sure why mono isn't behaving the same way as .NET.
I'm going to suggest an alternative approach: use a surrogate data transfer type to serialize your DoNotSerializeMe class. One way to do this is with Data Contract Surrogates. But are data contract surrogates supported on mono? The current version of the source code shows that they are, but this version shows it as [MonoTODO], so I cannot guarantee that your version of mono has a working implementation for DataContractJsonSerializer.DataContractSurrogate.
However, a manual surrogate property will always work. For instance, the following SerializeMe type can be serialized and serialized even though its DoNotSerializeMe member cannot be serialized directly:
public class DoNotSerializeMe
{
public readonly string WhyAmIHere;
readonly bool ProperlyConstructed; // data contract serializer does not call the constructor
public DoNotSerializeMe(string mystring)
{
Console.WriteLine(string.Format(" In DoNotSerializeMe constructor, mystring = \"{0}\"", mystring));
WhyAmIHere = mystring;
ProperlyConstructed = true;
}
public void Validate()
{
if (!ProperlyConstructed)
throw new InvalidOperationException("!ProperlyConstructed");
}
}
public class SerializeMe
{
[IgnoreDataMember]
public DoNotSerializeMe CannotBeSerializedDirectly;
public DoNotSerializeMeSurrogate DoNotSerializeMeSurrogate
{
get
{
if (CannotBeSerializedDirectly == null)
return null;
return new DoNotSerializeMeSurrogate { WhyAmIHereSurrogate = CannotBeSerializedDirectly.WhyAmIHere };
}
set
{
if (value == null)
CannotBeSerializedDirectly = null;
else
CannotBeSerializedDirectly = new DoNotSerializeMe(value.WhyAmIHereSurrogate);
}
}
public string SomeOtherField { get; set; }
}
public class DoNotSerializeMeSurrogate
{
public string WhyAmIHereSurrogate { get; set; }
}
Using this approach looks simpler than implementing ISerializable since all the members other than CannotBeSerializedDirectly continue to be serialized automatically. Note the use of [IgnoreDataMember]. It prevents the CannotBeSerializedDirectly member from being included in the implicit data contract for the class.
If you would prefer your surrogate to be private, or need to control the surrogate member names, you will need to give the containing type an explicit data contract:
[DataContract]
public class SerializeMe
{
[IgnoreDataMember]
public DoNotSerializeMe CannotBeSerializedDirectly;
[DataMember]
DoNotSerializeMeSurrogate DoNotSerializeMeSurrogate
{
get
{
if (CannotBeSerializedDirectly == null)
return null;
return new DoNotSerializeMeSurrogate { WhyAmIHereSurrogate = CannotBeSerializedDirectly.WhyAmIHere };
}
set
{
if (value == null)
CannotBeSerializedDirectly = null;
else
CannotBeSerializedDirectly = new DoNotSerializeMe(value.WhyAmIHereSurrogate);
}
}
[DataMember]
public string SomeOtherField { get; set; }
}
[DataContract]
class DoNotSerializeMeSurrogate
{
[DataMember]
public string WhyAmIHereSurrogate { get; set; }
}

How to serialize & deserialize static reference object?

i want to serialize & deserialize a object (this object has reference) using BinaryFormatter.
i have expected that 'DeserializedObject.Equals(A.Empty)' is same to below code.
but, a result is different.
in order to 'DeserializedObject == A.Empty', how to use serialize/deserialize ?
[Serializable]
public class A
{
private string ID = null;
private string Name = null;
public A()
{ }
public static A Empty = new A()
{
ID = "Empty",
Name = "Empty"
};
}
class Program
{
static void Main(string[] args)
{
A refObject = A.Empty; // Add reference with static object(Empty)
A DeserializedObject;
//Test
//before serialization, refObject and A.Empty is Same!!
if(refObject.Equals(A.Empty))
{
Console.WriteLine("refObject and A.Empty is the same ");
}
//serialization
using (Stream stream = File.Create("C:\\Users\\admin\\Desktop\\test.mbf"))
{
BinaryFormatter bin = new BinaryFormatter();
bin.Serialize(stream, refObject);
}
//Deserialization
using (Stream stream = File.Open("C:\\Users\\admin\\Desktop\\test.mbf", FileMode.Open))
{
BinaryFormatter bin = new BinaryFormatter();
DeserializedObject = (A)bin.Deserialize(stream);
}
//compare DeserializedObject and A.Empty again.
//After deserialization, DeserializedObject and A.Empty is Different!!
if (DeserializedObject.Equals(A.Empty))
{
Console.WriteLine("Same");
}
else
Console.WriteLine("Different");
}
}
The reason for this is that they are different objects! You can check this by printing their GetHashCode(). The reason for this is that in your code:
refObject is a reference to A.Empty (and thus the same object)
DeserialisedObject is NOT a copy; it is a new instance and so a
different object
However DeserializedObject should contain the same values (ID and Name). Note that refObject.ID will be the same object as A.Empty.ID; DeserialisedObject.ID will not, although is should contain (a copy of) the same data.
If you're just testing that deserialization is working, test that the values contained by DeserializedObject and A.Empty are the same.
If you have an immutable type which has one or more global singletons that represent standard values of the type (A.Empty in your case), you can implement the IObjectReference interface and make BinaryFormatter replace deserialized instances of the type with the appropriate, equivalent global singleton. Thus:
[Serializable]
public class A : IObjectReference
{
private string ID = null;
private string Name = null;
public A()
{ }
public static A Empty = new A()
{
ID = "Empty",
Name = "Empty"
};
#region IObjectReference Members
object IObjectReference.GetRealObject(StreamingContext context)
{
if (this.GetType() == Empty.GetType() // Type check because A is not sealed
&& this.ID == Empty.ID
&& this.Name == Empty.Name)
return Empty;
return this;
}
#endregion
}
And, to test:
public class TestClass
{
public static void Test()
{
A refObject = A.Empty; // Add reference with static object(Empty)
Test(refObject, true);
A dummy = new A(); // No global singleton for this one.
Test(dummy, false);
}
private static void Test(A refObject, bool shouldBeEqual)
{
Console.WriteLine(string.Format("refObject and A.Empty are {0}.", object.ReferenceEquals(refObject, A.Empty) ? "identical" : "different"));
var binary = BinaryFormatterHelper.ToBase64String(refObject);
var DeserializedObject = BinaryFormatterHelper.FromBase64String<A>(binary);
Console.WriteLine(string.Format("DeserializedObject and A.Empty are {0}.", object.ReferenceEquals(DeserializedObject, A.Empty) ? "identical" : "different"));
Debug.Assert(object.ReferenceEquals(refObject, A.Empty) == object.ReferenceEquals(DeserializedObject, A.Empty)); // No assert
Debug.Assert(shouldBeEqual == object.ReferenceEquals(refObject, DeserializedObject)); // No assert
}
}
public static class BinaryFormatterHelper
{
public static string ToBase64String<T>(T obj)
{
using (var stream = new MemoryStream())
{
new BinaryFormatter().Serialize(stream, obj);
return Convert.ToBase64String(stream.GetBuffer(), 0, checked((int)stream.Length)); // Throw an exception on overflow.
}
}
public static T FromBase64String<T>(string data)
{
return FromBase64String<T>(data, null);
}
public static T FromBase64String<T>(string data, BinaryFormatter formatter)
{
using (var stream = new MemoryStream(Convert.FromBase64String(data)))
{
formatter = (formatter ?? new BinaryFormatter());
var obj = formatter.Deserialize(stream);
if (obj is T)
return (T)obj;
return default(T);
}
}
}

How to overload muti-Generic class in C#?

How to define generic class for mapping muti-class?
I asked the question before,but i want like this
public class BaseRepositoryService<T1,T2,T3...Tn> where T:class
{
maplestory2Context mc = new maplestory2Context();
public T AddEntity(params object [] args)
{
for (int i = 0; i < args.Length; i++)
{
object obj = args[i];
}
}
}
but i am not sure the how many params I would use. thanks.
note:i changed my code below,but it is not convenience.
public class BaseRepositoryService<T> where T:class
{
maplestory2Context mc = new maplestory2Context();
public T AddEntity(T entity,int size)
{
if (size == 2)
{
}
return null;
}
}
public class BaseRepositoryService<T,T2>
where T : class
where T2:class
{
maplestory2Context mc = new maplestory2Context();
public T AddEntity(T entity, int size)
{
if (size == 2)
{
}
return null;
}
}
Answer to this question is you can't.
You need to define n no of classes for that even Microsoft has defined Func delegates 15 or 16 times. You can refer to msdn documentation for confirmation.
If you don't want write those classes manually you may opt for t4 templates as code generator.

Serialization of List<IMyInterface> using ISerializable

Thanks for taking a look!
I'm working on a new version of a product that is deployed in the field. I need to maintain the ability to deserialize exiting files from the older software.
Here is a contrived example:
I have a existing customer base with serialized files that they need to access. For the purposes of this question, they have a "Zoo" file with a List of Giraffes in it.
[Serializable]
public class Giraffe
: ISerializable
{
public int Age { get; private set; }
public Giraffe(int age)
{
Age = age;
}
void ISerializable.GetObjectData(SerializationInfo info, StreamingContext context)
{
info.AddValue("Age", Age);
}
[SecurityPermission(SecurityAction.Demand, SerializationFormatter = true)]
private Giraffe(SerializationInfo info, StreamingContext context)
{
Age = info.GetInt32("Age");
}
}
Now, we are deploying a new version of our "Zoo" software, and we are going to support anaimals other than Giraffes, we should have done this to begin with, but due to time constrains, we had to release 1.0 with a Giraffe-only feature set.
public interface IAnimal
{
int Age { get; }
}
[Serializable]
public class Animal
: IAnimal,
ISerializable
{
public int Age { get; private set; }
public Animal (int age)
{
Age = age;
}
void ISerializable.GetObjectData(SerializationInfo info, StreamingContext context)
{
info.AddValue("Age", Age);
}
[SecurityPermission(SecurityAction.Demand, SerializationFormatter = true)]
private Animal(SerializationInfo info, StreamingContext context)
{
Age = info.GetInt32("Age");
}
}
I'm using a custom serializationBinder to have old Giraffes deserialized as Animals
public class LegacyZooSerializationBinder
: SerializationBinder
{
public override Type BindToType(string assemblyName, string typeName)
{
if (typeName.StartsWith("Test.Giraffe"))
return typeof(Animal);
else if (typeName.StartsWith("System.Collections.Generic.List`1[[Test.Giraffe"))
return typeof(List<Animal>);
else return null;
}
}
The problem is that I wan't to have my Zoo class use a List as it's storage, not a List. I want to do this for two reasons, for future extendability, also, so that I can more easily mock things out for unit testing.
Deserializing the new IAnimal list is no problem. The problem comes when I want to deserialize the old style items. The Binder returns the correct type, the correct deserialization constructor is called, everything looks ok, but the List is actually full of null items. Once the IDeserializationCallback.OnDeserialization callback is called, the list is correct. I can't simply call IEnumerable.ConvertAll<>() on it, because it looks like the serialization framework is trying to find the exact same instance when it comes back to clean everything up, and ConvertAll will create a new list.
I have it working as of now, but I hope someone out there can help me clean this up, as it is not all that maintainable as of now. Here is what it takes to do it:
[Serializable]
public class Zoo
: ISerializable,
IDeserializationCallback
{
List<IAnimal> m_List = null;
List<Giraffe> m_LegacyList = null; //Just so that we can save an old-style zoo
//Temp copy of the list
List<Animal> m_List_Deserialization_Temp_Copy = null;
public Zoo(bool legacy)
{
m_List = new List<IAnimal>();
if (legacy)
{
//Create an old style zoo, just for the example
m_LegacyList = new List<Giraffe>();
m_LegacyList.Add(new Giraffe(0));
m_LegacyList.Add(new Giraffe(1));
}
else
{
m_List.Add(new Animal(0));
m_List.Add(new Animal(1));
}
}
public List<IAnimal> List
{
get { return m_List; }
}
void ISerializable.GetObjectData(SerializationInfo info, StreamingContext context)
{
if(m_LegacyList != null) //Save as an old style list if we have old data
info.AddValue("list", m_LegacyList);
else
info.AddValue("list", m_List);
}
[SecurityPermission(SecurityAction.Demand, SerializationFormatter = true)]
private Zoo(SerializationInfo info, StreamingContext context)
{
try
{
//New style
m_List = (List<IAnimal>)info.GetValue("list", typeof(List<IAnimal>));
}
catch (InvalidCastException)
{
//Old style
//Put it in a temp list, until the OnDeserialization callback is called, this will be a list, full of null items!
m_List_Deserialization_Temp_Copy = (List<Animal>)info.GetValue("list", typeof(List<Animal>));
}
}
void IDeserializationCallback.OnDeserialization(object sender)
{
if (m_List_Deserialization_Temp_Copy != null)
{
m_List = new List<IAnimal>();
//This works because IEnumerable<Animal> is covariant to IEnumerable<IAnimal>
m_List.AddRange(m_List_Deserialization_Temp_Copy);
}
}
}
Here is a basic test console app that shows serialization and deserialization for both cases:
static void Main(string[] args)
{
{
var item = new Zoo(false);
var formatter = new BinaryFormatter();
var stream = new MemoryStream();
formatter.Serialize(stream, item);
stream.Position = 0;
formatter.Binder = new LegacyZooSerializationBinder();
var deserialized = (Zoo)formatter.Deserialize(stream);
Debug.Assert(deserialized.List.Count == 2);
Debug.Assert(deserialized.List[0] != null);
Debug.Assert(deserialized.List[0].Age == 0);
Debug.Assert(deserialized.List[1] != null);
Debug.Assert(deserialized.List[1].Age == 1);
Console.WriteLine("New-style Zoo serialization OK.");
}
{
var item = new Zoo(true);
var formatter = new BinaryFormatter();
var stream = new MemoryStream();
formatter.Serialize(stream, item);
stream.Position = 0;
formatter.Binder = new LegacyZooSerializationBinder();
var deserialized = (Zoo)formatter.Deserialize(stream);
Debug.Assert(deserialized.List.Count == 2);
Debug.Assert(deserialized.List[0] != null);
Debug.Assert(deserialized.List[0].Age == 0);
Debug.Assert(deserialized.List[1] != null);
Debug.Assert(deserialized.List[1].Age == 1);
Console.WriteLine("Old-style Zoo serialization OK.");
}
Console.ReadKey();
}
Any suggestions would be greatly appreciated. I'm having a hard time finding good resources on this type of thing. Thanks!
Consider doing a one time conversion from the old files to the new format, preferably at install time and definitely after backing them up. That way you dont have to support this weird one-off serialization forever, and your codebase becomes drastically simpler.

Categories

Resources