I have two classes that need to be serialized to a file. Here is the basic Item class.
[Serializable()]
public class Item:ISerializable,...
{
......
private string _itemName;
[NonSerialized]
private Inventory _myInventory;
private double _weight;
....
event PropertyChangingEventHandler propertyChanging;
event PropertyChangingEventHandler INotifyPropertyChanging.PropertyChanging
{
add { propertyChanging += value;}
remove { propertyChanging -= value; }
}
public string Name {get;set;....}
public double Weight {get;set;...}
....
public void GetObjectData(SerializationInfo info, StreamingContext context)
{
info.AddValue("Name", _itemName);
info.AddValue("Weight", _weight);
Type t = this.GetType();
info.AddValue("TypeObj", t);
}
internal Item(SerializationInfo info, StreamingContext context)
{
_itemName = info.GetString("Name");
_weight = info.GetDouble("Weight");
}
And here is the Inventory class:
[Serializable()]
public class Inventory:......
{
private int _numOfProduct = 0;
private int _numOfItems = 0;
private Dictionary<string, Item> _inventoryDictionary = new Dictionary<string, Item>();
.....
public IEnumerable<Item> GetSortedProductsByName()
{
return _inventoryDictionary.OrderBy(key => key.Key).Select(key => key.Value).ToList();
}
.....
}
When I tested the binary serialization to files with:
//serialize
....
fs = File.OpenWrite(FileName); //FileName = "C:/temp/foo.bin"
b.Serialize(fs, products);
fs.Close();
....
//deserialize
Inventory products = new Inventory();
BinaryFormatter b = new BinaryFormatter();
fs = File.OpenRead(FileName);
products = (Inventory)b.Deserialize(fs);
...
When I tested the serialization it appears that the following code doesn't work as expected:
....
foreach (var item in products.GetSortedProductsByName())
{
Console.WriteLine(item.Name);
}
....
I debugged through the lines and found that item is always null although product is not null.
Any thoughts?
If anyone knows somewhere that I can find a similar sample implementation of my scenario, please let me know.
Related
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);
}
}
}
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.
Okay, I'm a little fuzzy on how this works or if it's possible. I want to serialize the Child class, but I don't actually want to serialize a Parent object when it does the Child.MyParent field... I just want the reference to be serialized. Is this possible and how would I go about it?
public class Parent
{
public Child New()
{
return new Child(this);
}
}
public class Child
{
public Parent MyParent;
public Child(Parent parent)
{
MyParent = parent;
}
}
Edit: I'm using DataContractSerializer, but I'm not opposed to switching to something else if necessary.
The XMLIgnoreAttribute can be applied to fields that you don's want serialized. For example,
public class Child
{
[XmlIgnore]
public Parent MyParent;
public Child(Parent parent)
{
MyParent = parent;
}
}
But as far as serializing a reference to the field, you'd have to provide more info on how you plan to persist the object that the reference points to. What is your reason to not just serialize the Parent member (in your case)? It's common to serialize all the public members that are needed.
If you just want to use serialization to clone, something like this should work:
private static Parent Clone(Parent parent)
{
Parent parentClone = null;
lock (m_lock) // serialize cloning.
{
IFormatter formatter = new BinaryFormatter();
MemoryStream stream = new MemoryStream();
using (stream)
{
formatter.Serialize(stream, parent);
stream.Seek(0, SeekOrigin.Begin);
parentClone = (Parent)formatter.Deserialize(stream);
}
}
return parentClone;
}
Sounds like you might need to implement your own serialization and deserialization functionality.
http://msdn.microsoft.com/en-us/library/system.runtime.serialization.iserializable.getobjectdata.aspx
Here's an extract from MSDN
[Serializable]
public class Person : ISerializable
{
private string name_value;
private int ID_value;
public Person() { }
protected Person(SerializationInfo info, StreamingContext context)
{
if (info == null)
throw new System.ArgumentNullException("info");
name_value = (string)info.GetValue("AltName", typeof(string));
ID_value = (int)info.GetValue("AltID", typeof(int));
}
[SecurityPermission(SecurityAction.LinkDemand,Flags = SecurityPermissionFlag.SerializationFormatter)]
public virtual void GetObjectData(SerializationInfo info, StreamingContext context)
{
if (info == null)
throw new System.ArgumentNullException("info");
info.AddValue("AltName", "XXX");
info.AddValue("AltID", 9999);
}
public string Name
{
get { return name_value; }
set { name_value = value; }
}
public int IdNumber
{
get { return ID_value; }
set { ID_value = value; }
}
}
Lets say, I have a class with objects in it.
namespace Class_Serialization
{
[Serializable]
public class Data
{
public string Name = "Example1";
public string place = "Torino";
public DateTime time = DateTime.Now;
}
}
I am trying to serialize it using ISerialization interface
[Serializable]
public class SerializeThisClass : ISerializable
{
public Data StreamThisData;
public SerializeThisClass()
{
}
public SerializeThisClass(Data _StreamThisData)
{
StreamThisData = _StreamThisData;
}
[SecurityPermission(SecurityAction.LinkDemand, Flags = SecurityPermissionFlag.SerializationFormatter)]
void ISerializable.GetObjectData(SerializationInfo info, StreamingContext context)
{
info.AddValue("Object Data", StreamThisData);
}
}
Now, When I serialize with the code below
Data DataToSerialize = new Data();
BinaryFormatter DataToBinary = new BinaryFormatter();
SerializeThisClass serialize = new SerializeThisClass(DataToSerialize);
SerializeIn SerializeInMem = new SerializeIn();
DataToBinary.Serialize(SerializeInMem.StreamOfData, serialize);
ListOfStreams.Add(SerializeInMem);
It serializes normally, but when I try to deserialize it, it gives me error
BinaryFormatter BinaryToData = new BinaryFormatter();
foreach (SerializeIn x in ListOfStreams)
{
x.StreamOfData.Position = 0;
SerializeThisClass DeserializeData = (SerializeThisClass)BinaryToData.Deserialize(x.StreamOfData);
MessageBox.Show("Name: " + DeserializeData.StreamThisData.Name + "\nPlace: " + DeserializeData.StreamThisData.place + "\nDateTime: " + DeserializeData.StreamThisData.time.ToString());
}
Error: $exception{"The constructor to deserialize an object of type
'Class_Serialization.SerializeThisClass' was not found."}
System.Exception {System.Runtime.Serialization.SerializationException}
If you are implementing ISerializable, you need a constructor of the signature:
protected YourType(SerializationInfo information, StreamingContext context) {}
which loads the data (basically, the reverse of GetObjectData). Presumably, with (untested):
StreamThisData = (Data)info.GetValue("Object Data", typeof(Data));
Try adding the constructor:
protected SerializeThisClass(SerializationInfo info, StreamingContext context)
{
}
http://msdn.microsoft.com/en-us/library/ms182343(v=vs.80).aspx
I need to make all my entities serializable. So I was thinking in a BaseEntity with a Backup and a Restore method. But in the restore I can't override the object with the saved one because this is read-only.
Any solution or some other way to get the serializable entities?
My code:
internal class BaseEntity
{
private MemoryStream ms = new MemoryStream();
private BinaryFormatter bf = new BinaryFormatter();
public void Backup()
{
bf.Serialize(ms, this);
}
public void Restore()
{
this = (BaseEntity)bf.Deserialize(ms);
}
}
The more common pattern is to not make it the responsibility of your objects to serialize/deserialize themselves; rather, use an external serializer:
var serializer = new DataContractJsonSerializer(typeof(YourClass));
var stream = ...;
YourClass yourObj = ...;
serializer.WriteObject(stream, yourObj);
var restoredObj = serializer.ReadObject(stream);
Edit: One way serialization can work is to use the System.Runtime.Serialization.Formatters.Binary.BinaryFormatter (or other implementation of IFormatter). To serialize an object you pass the object and a stream. To Deserialize the object, you pass a stream (positioned at the begining of your serialized data), and it returns the serialized object and all its depenedencies.
public static class EntityBackupServices
{
public static MemoryStream Backup (BaseEntity entity)
{
var ms = new MemoryStream();
Serialize (ms, entity);
ms.Position = 0;
return ms;
}
public static void Serialize (Stream stream, BaseEntity entity)
{
var binaryFormatter = new BinaryFormatter();
binaryFormatter.Serialize (stream, entity);
}
public static BaseEntity Restore (Stream stream)
{
var binaryFormatter = new BinaryFormatter();
var entity = (BaseEntity) binaryFormatter.Deserialize (stream);
return entity;
}
}
One thing a formatter don't do (though the FormatterServices class makes it possible) is modify existing objects. So you probably don't want to have an instance method called Deserialize. You can't really do this: new LionEntity().Deserialize () where it replaces the fields of an existing instance.
Note: You'll need to put Serializable over all your types. Any fields that can't be serialized (because it's either not a struct, or it's not marked as [Serializable] will need to be marked with NonSerialized.
// A test object that needs to be serialized.
[Serializable()]
public class BaseEntity
{
public int member1;
public string member2;
public string member3;
public double member4;
// A field that is not serialized.
[NonSerialized()] public MyRuntimeType memberThatIsNotSerializable;
public TestSimpleObject()
{
member1 = 11;
member2 = "hello";
member3 = "hello";
member4 = 3.14159265;
memberThatIsNotSerializable = new Form ();
}
public MemoryStream Backup ()
{
return EntityBackupServices.Backup (this);
}
}
Edit:
The way I've mentioned is a rather standard and accepted way. If you want to venture into hackdom, you can deserialize the object the way I've mentioned, then use reflection to set each field on your existing object to the value of the deserialized object.
public class BaseEntity
{
void Restore(Stream stream)
{
object deserialized = EntityBackupServices.RestoreDeserialize(stream);//As listed above
if (deserialized.GetType () != this.GetType ())
throw new Exception();
foreach (FieldInfo fi in GetType().GetFields())
{
fi.SetValue(this, fi.GetValue (deserialized));
}
}
}
public IEntidadBase Restore()
{
return (IEntidadBase)bf.Deserialize(ms);
}
#jacklondon how would you do EntitySerializer methods?
You can do serialization process with http://www.servicestack.net/ StackService.Text module for clean entities. You don't need any attribute (serializable/datacontract) in ms way.
public class EntityFoo
{
public string Bar { get; set; }
public EntityFoo (string bar)
{
Bar = bar;
}
}
public class EntityDumper //and the EntitySerializer
{
public static string Dump<T> (T entity)
{
return new TypeSerializer<T> ().SerializeToString (entity);
}
public static T LoadBack<T> (string dump)
{
return new TypeSerializer<T> ().DeserializeFromString (dump);
}
}
public class dump_usage
{
public void start ()
{
string dump = EntityDumper.Dump (new EntityFoo ("Space"));
EntityFoo loaded = EntityDumper.LoadBack<EntityFoo> (dump);
Debug.Assert (loaded.Bar == "Space");
}
}
I don't necessarily recommend this, but here is one pattern for an object that can persist and restore its own state using serialization that creates new instances:
public sealed class MyClass
{
private Data _data = new Data();
//Properties go here (access the public fields on _data)
public void Backup()
{
//Serialize Data
}
public void Restore()
{
//Deserialize Data and set new instance
}
private sealed class Data
{
//Public fields go here (they're private externally [because Data is private], but public to MyClass.)
}
}
Note that this only works if your serializer supports non-public classes. Worst-case, you have to make the nested class public, which is ugly, but doesn't hurt encapsulation (since the instance is private).