SerializationSurrogate problem with Arrays

G

gregbacchus

Can someone please help me and tell my why the following code is not
working, and hopefully how to get it working?

What I am trying to do is make a serialization surrogate that stores,
amongst other things, and array of objects that are also serialised by
the same surrogate. It serializes alright, but when it comes to
deserialization, the array comes out as an array (correct length) of
nulls. grrr... I'm pulling my hair out!!

Any help really appreciated.

Cheers
Greg



------ CODE START HERE ------
using System;
using System.Collections;
using System.IO;
using System.Runtime.Serialization;
using System.Runtime.Serialization.Formatters.Binary;

namespace SerializationSurrogateTest


{
public class Parent

{
private string _name;

public string Name { get { return _name; } set { _name = value; } }
private ArrayList _children;


public ArrayList Children { get { return _children; } }
public Parent()


{
_children = new ArrayList();


}


public override string ToString()

{
return Name;


}
}


public class Child

{
private string _name;

public string Name { get { return _name; } set { _name = value; } }
public Child()


{

}


public override string ToString()

{
return Name;


}
}


public class Surrogate : ISerializationSurrogate

{
#region ISerializationSurrogate Members


public void GetObjectData(object obj, SerializationInfo info,
StreamingContext context)


{
if( obj is Parent )

{
info.AddValue( "children", (Child[])((Parent)obj).Children.ToArray(
typeof( Child ) ), typeof( Child[] ) );
info.AddValue( "name", ((Parent)obj).Name );

}


else if( obj is Child )

{
info.AddValue( "name", ((Child)obj).Name );

}


Console.WriteLine( "Serializing {0}: {1}", obj.GetType().Name,
obj.ToString() );


}


public object SetObjectData(object obj, SerializationInfo info,
StreamingContext context, ISurrogateSelector selector)

{
if( obj is Parent )

{
//object c = info.GetValue( "children", typeof( object[] ) );
Child[] children = (Child[])info.GetValue( "children", typeof(
Child[] ) );
((Parent)obj).Children.Clear();
((Parent)obj).Children.AddRange( children );
((Parent)obj).Name = (string)info.GetValue( "name", typeof( object
) );

}


else if( obj is Child )

{
((Child)obj).Name = (string)info.GetValue( "name", typeof( object )
);


}


Console.WriteLine( "Deserializing {0}: {1}", obj.GetType().Name,
obj.ToString() );
return obj;


}


#endregion


}


/// <summary>
/// Summary description for StartHere.
/// </summary>
public class StartHere

{
/// <summary>
/// The main entry point for the application.
/// </summary>
[STAThread]
static void Main()

{
Child child1 = new Child();
child1.Name = "Child1";
Child child2 = new Child();
child2.Name = "Child2";
Parent parent = new Parent();
parent.Name = "Parent1";
parent.Children.Add( child1 );
parent.Children.Add( child2 );

SurrogateSelector selector = new SurrogateSelector();
selector.AddSurrogate( typeof( Parent ),
new StreamingContext( StreamingContextStates.All ),
new Surrogate() );
selector.AddSurrogate( typeof( Child ),
new StreamingContext( StreamingContextStates.All ),
new Surrogate() );


MemoryStream stream = new MemoryStream();
IFormatter formatter = new BinaryFormatter();
formatter.SurrogateSelector = selector;


formatter.Serialize( stream, parent );


stream.Position = 0;
object result = formatter.Deserialize( stream );
}
}

}
 
J

Justin Rogers

You are getting whacked by the intricacies of serialization. Your parent object
isn't constructed. It is blitted into memory directly. That means your
collections
are nulled out and aren't being set to a new ArrayList... I'd recommend writing
your parent as follows so that a new array-list is constructed upon access.

public class Parent {
private string _name;
public string Name { get { return _name; } set { _name = value; } }
private ArrayList _children;

public ArrayList Children {
get {
if ( _children == null ) {
_children = new ArrayList();
}

return _children;
}
}

public override string ToString() {
return Name;

}
}


--
Justin Rogers
DigiTec Web Consultants, LLC.
Blog: http://weblogs.asp.net/justin_rogers

gregbacchus said:
Can someone please help me and tell my why the following code is not
working, and hopefully how to get it working?

What I am trying to do is make a serialization surrogate that stores,
amongst other things, and array of objects that are also serialised by
the same surrogate. It serializes alright, but when it comes to
deserialization, the array comes out as an array (correct length) of
nulls. grrr... I'm pulling my hair out!!

Any help really appreciated.

Cheers
Greg



------ CODE START HERE ------
using System;
using System.Collections;
using System.IO;
using System.Runtime.Serialization;
using System.Runtime.Serialization.Formatters.Binary;

namespace SerializationSurrogateTest


{
public class Parent

{
private string _name;

public string Name { get { return _name; } set { _name = value; } }
private ArrayList _children;


public ArrayList Children { get { return _children; } }
public Parent()


{
_children = new ArrayList();


}


public override string ToString()

{
return Name;


}
}


public class Child

{
private string _name;

public string Name { get { return _name; } set { _name = value; } }
public Child()


{

}


public override string ToString()

{
return Name;


}
}


public class Surrogate : ISerializationSurrogate

{
#region ISerializationSurrogate Members


public void GetObjectData(object obj, SerializationInfo info,
StreamingContext context)


{
if( obj is Parent )

{
info.AddValue( "children", (Child[])((Parent)obj).Children.ToArray(
typeof( Child ) ), typeof( Child[] ) );
info.AddValue( "name", ((Parent)obj).Name );

}


else if( obj is Child )

{
info.AddValue( "name", ((Child)obj).Name );

}


Console.WriteLine( "Serializing {0}: {1}", obj.GetType().Name,
obj.ToString() );


}


public object SetObjectData(object obj, SerializationInfo info,
StreamingContext context, ISurrogateSelector selector)

{
if( obj is Parent )

{
//object c = info.GetValue( "children", typeof( object[] ) );
Child[] children = (Child[])info.GetValue( "children", typeof(
Child[] ) );
((Parent)obj).Children.Clear();
((Parent)obj).Children.AddRange( children );
((Parent)obj).Name = (string)info.GetValue( "name", typeof( object
) );

}


else if( obj is Child )

{
((Child)obj).Name = (string)info.GetValue( "name", typeof( object )
);


}


Console.WriteLine( "Deserializing {0}: {1}", obj.GetType().Name,
obj.ToString() );
return obj;


}


#endregion


}


/// <summary>
/// Summary description for StartHere.
/// </summary>
public class StartHere

{
/// <summary>
/// The main entry point for the application.
/// </summary>
[STAThread]
static void Main()

{
Child child1 = new Child();
child1.Name = "Child1";
Child child2 = new Child();
child2.Name = "Child2";
Parent parent = new Parent();
parent.Name = "Parent1";
parent.Children.Add( child1 );
parent.Children.Add( child2 );

SurrogateSelector selector = new SurrogateSelector();
selector.AddSurrogate( typeof( Parent ),
new StreamingContext( StreamingContextStates.All ),
new Surrogate() );
selector.AddSurrogate( typeof( Child ),
new StreamingContext( StreamingContextStates.All ),
new Surrogate() );


MemoryStream stream = new MemoryStream();
IFormatter formatter = new BinaryFormatter();
formatter.SurrogateSelector = selector;


formatter.Serialize( stream, parent );


stream.Position = 0;
object result = formatter.Deserialize( stream );
}
}

}
 
G

Greg Bacchus

Thanks for you reply Justin, but this is another issue all together.
One which I already fixed (just didn't show in this code) by calling
the default constructor explicity - as shown below. My actual problem
is that
info.GetValue( "children", typeof( ArrayList ) )
comes back as an arraylist of nulls.

Greg


public object SetObjectData(object obj, SerializationInfo info,
StreamingContext context, ISurrogateSelector selector)
{
ConstructorInfo constructor = obj.GetType().GetConstructor( new
Type[0] );
obj = constructor.Invoke( new object[0] );
....
 
J

Justin Rogers

Using the code I'm about to paste below, which is just your code with
the parent change the serialization is clearly working based on the last
two writeline calls.

using System;
using System.Collections;
using System.IO;
using System.Runtime.Serialization;
using System.Runtime.Serialization.Formatters.Binary;

namespace SerializationSurrogateTest {
public class Parent {
private string _name;
public string Name { get { return _name; } set { _name = value; } }
private ArrayList _children;

public ArrayList Children {
get {
if ( _children == null ) {
_children = new ArrayList();
}

return _children;
}
}

public override string ToString() {
return Name;

}
}


public class Child
{
private string _name;

public string Name { get { return _name; } set { _name = value; } }
public Child()


{

}


public override string ToString()

{
return Name;


}
}


public class Surrogate : ISerializationSurrogate {
public void GetObjectData(object obj, SerializationInfo info,
StreamingContext context) {
if( obj is Parent ) {
info.AddValue( "children",
(Child[])((Parent)obj).Children.ToArray(typeof( Child ) ), typeof( Child[] ) );
info.AddValue( "name", ((Parent)obj).Name );
} else if( obj is Child ) {
info.AddValue( "name", ((Child)obj).Name );
}


Console.WriteLine( "Serializing {0}: {1}", obj.GetType().Name,
obj.ToString() );
}

public object SetObjectData(object obj, SerializationInfo info,
StreamingContext context, ISurrogateSelector selector) {
if( obj is Parent ) {
//object c = info.GetValue( "children", typeof( object[] ) );
Child[] children = (Child[])info.GetValue( "children",
typeof(Child[] ) );
((Parent)obj).Children.Clear();
((Parent)obj).Children.AddRange( children );
((Parent)obj).Name = (string)info.GetValue( "name", typeof(
object) );

} else if( obj is Child ) {
((Child)obj).Name = (string)info.GetValue( "name", typeof(
object ) );

}

Console.WriteLine( "Deserializing {0}: {1}", obj.GetType().Name,
obj.ToString() );
return obj;
}
}


public class StartHere {
[STAThread]
private static void Main() {
Child child1 = new Child();
child1.Name = "Child1";

Child child2 = new Child();
child2.Name = "Child2";

Parent parent = new Parent();
parent.Name = "Parent1";

parent.Children.Add( child1 );
parent.Children.Add( child2 );

SurrogateSelector selector = new SurrogateSelector();
selector.AddSurrogate( typeof( Parent ), new StreamingContext(
StreamingContextStates.All ), new Surrogate() );
selector.AddSurrogate( typeof( Child ), new StreamingContext(
StreamingContextStates.All ), new Surrogate() );

MemoryStream stream = new MemoryStream();
IFormatter formatter = new BinaryFormatter();
formatter.SurrogateSelector = selector;
formatter.Serialize( stream, parent );


stream.Position = 0;
Parent result = (Parent) formatter.Deserialize( stream );

for(int i = 0; i < result.Children.Count; i++) {
Console.WriteLine(result.Children);
}
}
}
}


--
Justin Rogers
DigiTec Web Consultants, LLC.
Blog: http://weblogs.asp.net/justin_rogers

Greg Bacchus said:
Thanks for you reply Justin, but this is another issue all together.
One which I already fixed (just didn't show in this code) by calling
the default constructor explicity - as shown below. My actual problem
is that
info.GetValue( "children", typeof( ArrayList ) )
comes back as an arraylist of nulls.

Greg


public object SetObjectData(object obj, SerializationInfo info,
StreamingContext context, ISurrogateSelector selector)
{
ConstructorInfo constructor = obj.GetType().GetConstructor( new
Type[0] );
obj = constructor.Invoke( new object[0] );
...
 
G

Greg Bacchus

That doesn't work on either of my computers. I copied and pasted your
code exactly.
result.Children is null for both of the entries.

Cheers
Greg
 
J

Justin Rogers

Interesting, then we have an impasse, I can't help unless I can repro your
situation exactly, and for me the code works... I'll play around a bit with
the code and see if I can get it to fail.

So with the code, you got two empty strings printed out instead of Child1 and
Child2?
 
G

Greg Bacchus

yeah, and if i put a breakpoint in, the value is null... not even a
Child() object without the values set.
maybe a framework version issue??? I'm using v1.1.4322.573
 
J

Justin Rogers

I went back and ran it on V1.1 and I get your issue. Now that I have
a repro I'll try and work up a test case tonight and throw it on a Whidbey
build tomorrow. I think I've found your problem, but I'll have to drastically
rewrite your program to get things working properly...
 
J

Justin Rogers

You need to deserialize the collection or array, and then set it on the object
directly. That
way when your elements are created later they get set on the collection or array
that was
just deserialized.

This seems like a breaking change of some sort, but surrogates aren't widely
used.

--
Justin Rogers
DigiTec Web Consultants, LLC.
Blog: http://weblogs.asp.net/justin_rogers

using System;
using System.Collections;
using System.IO;
using System.Runtime.Serialization;
using System.Runtime.Serialization.Formatters.Binary;

namespace SerializationSurrogateTest {
public class Parent {
private string _name;
public string Name { get { return _name; } set { _name = value; } }
private ArrayList _children;

public ArrayList Children {
get {
if ( _children == null ) {
_children = new ArrayList();
}

return _children;
}
set {
_children = value;
}
}

public override string ToString() {
return Name;

}
}


public class Child {
private string _name;

public string Name { get { return _name; } set { _name = value; } }

public override string ToString() {
return Name;
}
}


public class Surrogate : ISerializationSurrogate {
public void GetObjectData(object obj, SerializationInfo info,
StreamingContext context) {
if( obj is Parent ) {
info.AddValue( "children", ((Parent)obj).Children );
info.AddValue( "name", ((Parent)obj).Name );
} else if( obj is Child ) {
info.AddValue( "name", ((Child)obj).Name );
}


Console.WriteLine( "Serializing {0}: {1}", obj.GetType().Name,
obj.ToString() );
}

public object SetObjectData(object obj, SerializationInfo info,
StreamingContext context, ISurrogateSelector selector) {
if( obj is Parent ) {
//object c = info.GetValue( "children", typeof( object[] ) );
((Parent)obj).Children = ((ArrayList)info.GetValue( "children",
typeof(ArrayList) ));
((Parent)obj).Name = (string)info.GetValue( "name", typeof(
object) );

} else if( obj is Child ) {
((Child)obj).Name = (string)info.GetValue( "name", typeof(
object ) );

}

Console.WriteLine( "Deserializing {0}: {1}", obj.GetType().Name,
obj.ToString() );
return obj;
}
}


public class StartHere {
[STAThread]
private static void Main() {
Child child1 = new Child();
child1.Name = "Child1";

Child child2 = new Child();
child2.Name = "Child2";

Parent parent = new Parent();
parent.Name = "Parent1";

parent.Children.Add( child1 );
parent.Children.Add( child2 );

SurrogateSelector selector = new SurrogateSelector();
selector.AddSurrogate( typeof( Parent ), new StreamingContext(
StreamingContextStates.All ), new Surrogate() );
selector.AddSurrogate( typeof( Child ), new StreamingContext(
StreamingContextStates.All ), new Surrogate() );

MemoryStream stream = new MemoryStream();
IFormatter formatter = new BinaryFormatter();
formatter.SurrogateSelector = selector;
formatter.Serialize( stream, parent );


stream.Position = 0;
Parent result = (Parent) formatter.Deserialize( stream );

for(int i = 0; i < result.Children.Count; i++) {
Console.WriteLine(result.Children);
}
}
}
}
 
G

Greg Bacchus

Brilliant. Thanks for that... now I've got to try to figure out how to
fit that into my actual project, which is of course a bit more
complexed.
 
J

Justin Rogers

The changes you need to know are:

V1: Array members are instantiated during array creation
V1.1: Array members are instantiated after array creation

I'm not sure I'm 100% happy with how things work right now, and I'll fire off
some emails to find out why tomorrow morning.
 

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Top