views:

64

answers:

4

Hi,

I need to persist an object that is not marked with the serializable attribute. The object is from a 3rd party library which I cannot change.

I need to store it in a persist place, like for example the file system, so the optimal solution would be to serialize the object to a file, but since it isn't marked as serializable, that is not a straight forward solution.

It's a pretty complex object, which also holds a collection of other objects.

Do you guys have any input on how to solve this? The code will never run in a production environment, so I'm ok with almost any solution and performance.

+2  A: 

I don't know if it is overkill for your usage, but I have been playing around with db4o lately. It will persist any object, just call IObjectContainer.Store(object), and it is lightweight and file-based. Does not require any installation.

I haven't had any problems with it yet.

driis
+1  A: 

This is one way you could do it:

http://www.codeproject.com/KB/dotnet/Surrogate_Serialization.aspx

here is the msdn link showing it:

http://msdn.microsoft.com/en-us/magazine/cc188950.aspx

Kevin
+5  A: 

XmlSerializer may be a useful first thing to try, if the types are public etc

If that fails, v2 of protobuf-net (in progress, you'd need to build from source, but I can help) works with unattributed objects, so ideal for types outside your control - you just need to tell it what to include (via a DSL). The v2 code isn't complete, but it covers most common scenarios, including collections etc (the incomplete work is mainly callbacks and enums).

Marc Gravell
protobuf-net looks pretty nice. Does it require parameterless constructors?
lasseeskildsen
@lasseeskildsen - at the moment it does, yes - but since I own it I'm sure I could add the WCF approach (of not calling any ctor). It would take only moments (it is just a call to `FormatterServices.GetUninitializedObject`).
Marc Gravell
If you have an example of the 3rd-party API I could proabably whip up a v2 example.
Marc Gravell
Unfortunately, the 3rd party API is MS Commerce Server. It uses a HTTP module to cache a lot of stuff into a static variable at the first request, so I have created another module, that uses reflection to grab that field, and set it again. So I only need somewhere to store the value when the app restarts.
lasseeskildsen
I'm not hugely familiar with Commerce Server, so I can't comment directly on that.
Marc Gravell
@Marc: thanks for the tip on `FormatterServices.GetUninitializedObject` I was working on building a quick set of methods for this and couldn't figure out how to create an object without the parameterless constructor. That's a slick trick... THANKS!
Matthew Whited
I'll try out protobuf-net - thanks a lot for the info!
lasseeskildsen
+2  A: 

You could write a recursive method that would run down the object graph using reflection to persist the object... Putting it back could be much more difficult. Who knows if any of those objects are holding references to unmanaged or system resources. If i was to do anything this nuts, I would go for the .GetFields(...) method on the type.

Another idea...

If you are only doing this to speed up development why not wrap their clases with your own adapter classes. This would allow you to replace the third party libraries with your own simplifed mock classes and allow for better chance for replacement and reuse later.

Sick as it is... This was easier then I thought it would be. (While this works... please consider wrapping the third party classes.)

public static class Tools
{
    public static XElement AsXml(this object input)
    {
        return input.AsXml(string.Empty);
    }
    public static XElement AsXml(this object input, string name)
    {
        if (string.IsNullOrEmpty(name))
            name = input.GetType().Name;

        var xname = XmlConvert.EncodeName(name);

        if (input == null)
            return new XElement(xname);

        if (input is string || input is int || input is float /* others */)
            return new XElement(xname, input);

        var type = input.GetType();
        var fields = type.GetFields(BindingFlags.Instance |
                                    BindingFlags.NonPublic)
                         .Union(type.GetFields(BindingFlags.Instance |
                                               BindingFlags.Public));

        var elems = fields.Select(f => f.GetValue(input)
                                        .AsXml(f.Name));

        return new XElement(xname, elems);
    }
    public static void ToObject(this XElement input, object result)
    {
        if (input == null || result == null)
            throw new ArgumentNullException();

        var type = result.GetType();
        var fields = type.GetFields(BindingFlags.Instance |
                                    BindingFlags.NonPublic)
                         .Union(type.GetFields(BindingFlags.Instance |
                                               BindingFlags.Public));

        var values = from elm in input.Elements()
                     let name = XmlConvert.DecodeName(elm.Name.LocalName)
                     join field in fields on name equals field.Name
                     let backType = field.FieldType
                     let val = elm.Value
                     let parsed = backType.AsValue(val, elm)
                     select new
                     {
                         field,
                         parsed
                     };

        foreach (var item in values)
            item.field.SetValue(result, item.parsed);            
    }

    public static object AsValue(this Type backType,
                                      string val,
                                      XElement elm)
    {
        if (backType == typeof(string))
            return (object)val;
        if (backType == typeof(int))
            return (object)int.Parse(val);
        if (backType == typeof(float))
            return (float)int.Parse(val);

        object ret = FormatterServices.GetUninitializedObject(backType);
        elm.ToObject(ret);
        return ret;
    }
}
public class Program
{
    public static void Main(string[] args)
    {
        var obj = new { Matt = "hi", Other = new { ID = 1 } };
        var other = new { Matt = "zzz", Other = new { ID = 5 } };
        var ret = obj.AsXml();
        ret.ToObject(other);
        Console.WriteLine(obj); //{ Matt = hi, Other = { ID = 1 } }
        Console.WriteLine(other); //{ Matt = hi, Other = { ID = 1 } }
    }
}
Matthew Whited
+1 Wrapping is a splendid idea in a scenario like this.
Filburt
Yeah, I just tried building a quick solution to persist and restore objects based on private values. Persisting is fairly easy; the problem comes with restoring them. If you don't have access to a parametter less constructor it may be impossible.
Matthew Whited
I hold no responsibility if the above code causes the world to explode and all life in the universe comes to an abrupt ending
Matthew Whited
Cool - I'll take a look at it. Thanks :-)
lasseeskildsen
This only supports string, int, and float right now. you may need to add other native types, or at the very least change how the type selection code works. Enjoy, have fun... and please don't try this at home ;)
Matthew Whited