2

I am writing an agnostic viewer for a collection of systems that I'm working with. This viewer will show me the generic structure of my data without needing to know the context of the specific system.

I'm trying to deserialise a memory stream which contains only a type Foo<T> where Foo<T> inherits from Foo. From the agnostic viewers point of view, all the data that I require is in Foo. The <T> part is irrelevant.

Type T is defined in another assembly(s). Under normal operation, the system obviously has all appropriate contextual assemblies loaded. The problem is that when running the viewer, none of the contextual assemblies are loaded. when I attempt to deserialise the instance of Foo, I obviously get an exception because the referenced assembly is not loaded.

I'm trying to detect whether or not I have all required referenced assemblies loaded and thus know whether to try to deserialise the data, or reconstruct the data that I require from other aspects of the class.

I know I can do this using a very simple exception try/ catch block, but, this is not an exception case. I know this is going to happen hundreds, if not thousands of times when I load my data, and this could cause me a nightmare, as I like to have break on exception turned on. I also subscribe to the school of thought that says "Exception - the hint is in the name" and thus Exceptions should not form part of your primary case code.

--------edit 21/10/2013------------

see here for a full illustrative example, but here are the important bits:

Foo class, defined in common:

[Serializable]
public class Foo
{
    public string Agnostic { get; set; }
}

[Serializable]
public class Foo<T> : Foo
{
    public string Contextual { get; set; }
}

Contextual saving:

BinaryFormatter bf = new BinaryFormatter();
FileInfo tempFile = TempFileGetter.GetTempFile();


Foo<Bar> fooBar = new Foo<Bar>();
fooBar.Agnostic = "Agnostic";
fooBar.Contextual = "Contextual";


using (var fs = tempFile.OpenWrite())
{
   bf.Serialize(fs, fooBar);
   fs.Flush();
}

Agnostic loading:

BinaryFormatter bf = new BinaryFormatter();
FileInfo tempFile = TempFileGetter.GetTempFile();

using (var fs = tempFile.OpenRead())
{
   Foo foo = (Foo)bf.Deserialize(fs);
   Controls.DataContext = foo;
}

I mean, there is nothing rocket science in this code, and, if the "agnostic" viewer loads the context viewer as a reference, then it loads fine, but, I don't want to do this, as we won't always have the the contextual libraries to load.

Immortal Blue
  • 1,691
  • 13
  • 27

2 Answers2

0

What I did was create a serialization container which analyses it's contents to see which references are needed and is serialized separately:

[serializable]
public class Container
{
    private IEnumerable<object> data;
    public Container(IEnumerable data);

    public string[] GetFullyQualifiedReferences();        
}

So you put whatever data you like in here, and you get a list of fully qualified assembly names, which you then store seperately.

Assuming Wibble is the class that is serializing the Foo

public class Wibble : ISerializable
{        

    public string Agnostic { get { return agnostic; } }

    public void GetObjectData(SerializationInfo info, StreamingContext context)
    {
        //construct a container based on my data.
        Container container = new Container(new object[]{myFoo});
        info.AddValue("RequiredReferences",container.GetFullyQualifiedReferences());

        byte[] containerData = Serialize(container);
        info.AddValue("TypeUnsafeData",containerData);

        //store some safe typed versions of the context data, if necessary.
        info.AddValue("SafeValues", GetSafeValues())
    }

    public Wibble(SerializationInfo info, StreamingContext context)
    { 
        var requiredAssemblies = 
           (string[])info.GetValue("RequiredReferences",typeof(string[]));

        if(AreAssembliesLoaded(requiredAssemblies )))
        {
           //deserialise the container as normal
        }
        else
        {
           //instead, load the "safe" data that we previously stored.
        }

    }

}

I know this doesn't make perfect sense given the illustration, but the illustration is a slightly imperfect abstraction of my implementation problem, but the solution should work for both (I know it works for my implementation!)

You could go the next step and have a container in a container listing the specific required assemblies for any container, but that's not really necessary in this illustration.

------Update-------

There is a slight problem with this implementation as it stands, and that is if you update the contextual assembly version numbers, the deserializer will not find the old assembly. so, you either need to:

Provide some sort of mechanism for allowing the deseralize code to see if it can find a modern version of the assembly and then query it,

---or---

Update the equality of the fully qualified name to not be sensitive to versioning.

Immortal Blue
  • 1,691
  • 13
  • 27
-1

The BinaryFormatter includes this information, try reading the serialized data in to a MemoryStream and converting it to a string

// There's probably a better way to do this:
new String(memoryStream.GetBuffer().Select<byte, char>(b => (char)b).ToArray());

For Assembly.Name.MyObject<int> You will end up with something that looks like a pain to parse, but it should be do-able:

[ÿÿÿÿBAssembly.Name, Version=1.0.0.0, Culture=neutral, PublicKeyToken=nullgMyObject`1[[System.Int32, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089]]n1n2str ]

Alternatively, if you have control of the serialization, serialize the objects with the information you need in the file first (for e.g. just the type and assembly of T), then the data from the BinaryFormatter. (I can expand on this if you want).

To see if any object is loaded, you could use Type.GetType("Assembly.Name.Space.ClassName") as it returns null if the type isn't found, but there are other methods listed for the question "how to check if namespace, class, or method exists" that could be used instead.

Community
  • 1
  • 1
Rob Church
  • 6,783
  • 3
  • 41
  • 46
  • I'm not sure I'm comfortable with that as a solution, it looks awfully like breaking the encapsulation of the serialization engine... If the format that binary serializer uses changes, or if I change my serialization engine, things will go bang and it will not be obvious why... – Immortal Blue Oct 22 '13 at 13:34
  • If you change your serialization engine things will go bang with any pre-existing saved data... But you're right, this does break the encapsulation. So write your own serialization engine that writes a bit of metadata (class/assembly name) before calling a built-in engine to do the bulk of the work. Deserialization can read that metadata, check that the class exists, then pass it to the built-in engine if appropriate. If you think you might change that engine, your custom class could include a flag to say which engine you were using allowing your data to be read in the future. – Rob Church Oct 22 '13 at 14:15
  • Oh, that's sort of what you've done but with a class structure instead of data in a file. – Rob Church Oct 22 '13 at 14:20
  • Well, I'm not examining a raw data stream ;) but yeah, it's a similar idea. When I was suggesting engine changes, I wasn't implying multi serialization engine support (that's a whole other can of worms!), just the danger of assuming implementation details outside of an encapsulated context. – Immortal Blue Oct 22 '13 at 14:47