views:

834

answers:

1

I've been having some problem serializing my objects and have narrowed the problem down to a specific case (see code below). I was getting the following error:

Error 1 Invalid Resx file. Could not load type Serialisation.Harness.Blob, Serialisation, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null which is used in the .RESX file. Ensure that the necessary references have been added to your project. Line 129, position 5. ...

Now the really strange thing is that restarting Visual Studio causes the error to go away and the code to work but then after a seemingly random number of builds (during which the said code is not changed) it will break again.

Can you see what I'm doing wrong/missing out?

Many thanks in advance,

Meto

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.ComponentModel;
using System.Windows.Forms;
using System.Windows.Forms.Design; using System.ComponentModel.Design;

namespace Serialisation.Harness
{    

    [Serializable]
    public class Blob
    {
        public Blob()
        {
        }
    }

    [Serializable]
    public class Basic
    {

        private List<Blob> blobs;
        public List<Blob> Blobs
        {
            get { return blobs; }
            set { this.blobs= value; }
        }

        public Basic()
        {
            basics = new List<Blob>();
        }

    }

    public class BasicComponent : Component
    {

        private Basic basic = new Basic();

        private IContainer components = new Container();

        public List<Blob> Blobs
        {
            get { return basic.Blobs; }
            set { basic.Blobs= value; }
        }

        public BasicComponent(IContainer container)
        {
            container.Add(this);
        }

    }

}
+3  A: 

First of all, the Serializable attribute is not used for designer serialization. When serializing objects, the designer will serialize to the resource file when it doesn't know how to write it to the designer code. This writes it out to the resx as a blob using an InstanceDescriptor for the object type's default constructor (this loses any property values you might also want to include). This is what is happening for the Blobs property as the designer doesn't serialize generic lists nicely (it does know how to serialize arrays though).

In order to retain the information inside these persisted objects, you will need to create a TypeConverter that specifies a different constructor in the InstanceDescriptor (one that actually takes some state to describe the properties, such as your Blobs property). For example, if you added a constructor to your BasicComponent type that takes an IEnumerable<Blob>, you could then get an InstanceDescriptor to that constructor, passing in an array of Blobs (you'd create a new List<Blob> around that in the constructor). Because the designer knows how to persist an InstanceDescriptor to code, and because it knows how to persist arrays to code, it would then add this to your designer code rather than the resx.

You can also implement a CodeDomSerializer to specify the code that is used to describe your instance, which the designer can use to save your object to the designer code rather than the resx.

Type Converter

To use the type converter approach, you might do something like this:

public class BasicComponentTypeConverter : TypeConverter
{
    public override bool CanConvertTo(ITypeDescriptorContext context, Type destinationType)
    {
        bool canConvert = base.CanConvertTo(context, destinationType);

        if (!canConvert &&
            (destinationType == typeof(InstanceDescriptor))
        {
            canConvert = true;
        }

        return canConvert;
    }

    public override object ConvertTo(ITypeDescriptorContext context, CultureInfo culture, object value, Type destinationType)
    {
        object conversion = null;

        if (culture == null)
        {
            culture = CultureInfo.CurrentCulture;
        }

        BasicComponent component = value as BasicComponent;
        if (basicComponent != null)
        {
            if (destinationType == typeof(InstanceDescriptor))
            {
               // Note that we convert the blobs to an array as this makes for nicer persisted code output.
               // Without it, we might just get a resource blob which is not human-readable.
               conversion = new InstanceDescriptor(
                   typeof(BasicComponent).GetConstructor(new Type[] { typeof(IEnumerable<Blob>) }),
                   new object[] { basicComponent.Blobs.ToArray() },
                   true);
            }
        }

        if (conversion == null)
        {
            conversion = base.ConvertTo(context, culture, value, destinationType);
        }

        return conversion;
    }
}

Note that you may need to write a type converter for the Blob type as well. To attach the type converter to the type, just declare the TypeConverter attribute on the class the type converter will convert, i.e. BasicConverter for the example above.

Jeff Yates
Thank you so much, unfortunately I'm new here so I can't vote you up for this.
Always happy to help.
Jeff Yates