Can I serialize a C# Type object?

Viewed 70770

I'm trying to serialize a Type object in the following way:

Type myType = typeof (StringBuilder);
var serializer = new XmlSerializer(typeof(Type));
TextWriter writer = new StringWriter();
serializer.Serialize(writer, myType);

When I do this, the call to Serialize throws the following exception:

"The type System.Text.StringBuilder was not expected. Use the XmlInclude or SoapInclude attribute to specify types that are not known statically."

Is there a way for me to serialize the Type object? Note that I am not trying to serialize the StringBuilder itself, but the Type object containing the metadata about the StringBuilder class.

6 Answers

I wasn't aware that a Type object could be created with only a string containing the fully-qualified name. To get the fully qualified name, you can use the following:

string typeName = typeof (StringBuilder).FullName;

You can then persist this string however needed, then reconstruct the type like this:

Type t = Type.GetType(typeName);

If you need to create an instance of the type, you can do this:

object o = Activator.CreateInstance(t);

If you check the value of o.GetType(), it will be StringBuilder, just as you would expect.

I had the same problem, and my solution was to create a SerializableType class. It freely converts to and from System.Type, but it serializes as a string. All you have to do is declare the variable as a SerializableType, and from then on you can refer to it as System.Type.

Here is the class:

// a version of System.Type that can be serialized
[DataContract]
public class SerializableType
{
    public Type type;

    // when serializing, store as a string
    [DataMember]
    string TypeString
    {
        get
        {
            if (type == null)
                return null;
            return type.FullName;
        }
        set
        {
            if (value == null)
                type = null;
            else
            {
                type = Type.GetType(value);
            }
        }
    }

    // constructors
    public SerializableType()
    {
        type = null;
    }
    public SerializableType(Type t)
    {
        type = t;
    }

    // allow SerializableType to implicitly be converted to and from System.Type
    static public implicit operator Type(SerializableType stype)
    {
        return stype.type;
    }
    static public implicit operator SerializableType(Type t)
    {
        return new SerializableType(t);
    }

    // overload the == and != operators
    public static bool operator ==(SerializableType a, SerializableType b)
    {
        // If both are null, or both are same instance, return true.
        if (System.Object.ReferenceEquals(a, b))
        {
            return true;
        }

        // If one is null, but not both, return false.
        if (((object)a == null) || ((object)b == null))
        {
            return false;
        }

        // Return true if the fields match:
        return a.type == b.type;
    }
    public static bool operator !=(SerializableType a, SerializableType b)
    {
        return !(a == b);
    }
    // we don't need to overload operators between SerializableType and System.Type because we already enabled them to implicitly convert

    public override int GetHashCode()
    {
        return type.GetHashCode();
    }

    // overload the .Equals method
    public override bool Equals(System.Object obj)
    {
        // If parameter is null return false.
        if (obj == null)
        {
            return false;
        }

        // If parameter cannot be cast to SerializableType return false.
        SerializableType p = obj as SerializableType;
        if ((System.Object)p == null)
        {
            return false;
        }

        // Return true if the fields match:
        return (type == p.type);
    }
    public bool Equals(SerializableType p)
    {
        // If parameter is null return false:
        if ((object)p == null)
        {
            return false;
        }

        // Return true if the fields match:
        return (type == p.type);
    }
}

and an example of usage:

[DataContract]
public class A
{

    ...

    [DataMember]
    private Dictionary<SerializableType, B> _bees;

    ...

    public B GetB(Type type)
    {
        return _bees[type];
    }

    ...

}

You might also consider using AssemblyQualifiedName instead of Type.FullName - see comment by @GreyCloud

According to the MSDN documentation of System.Type [1] you should be able to serialize the System.Type object. However, as the error is explicitly referring to System.Text.StringBuilder, that is likely the class that is causing the serialization error.

[1] Type Class (System) - http://msdn.microsoft.com/en-us/library/system.type.aspx

I came across this issue trying to do binary serialization in .net standard 2.0. I ended up solving the problem using a custom SurrogateSelector and SerializationBinder.

The TypeSerializationBinder was required because the framework was having trouble resolving System.RuntimeType before it got SurrogateSelector. I don't really understand why the type must be resolved before this step though...

Here is the code:

// Serializes and deserializes System.Type
public class TypeSerializationSurrogate : ISerializationSurrogate {
    public void GetObjectData(object obj, SerializationInfo info, StreamingContext context) {
        info.AddValue(nameof(Type.FullName), (obj as Type).FullName);
    }

    public object SetObjectData(object obj, SerializationInfo info, StreamingContext context, ISurrogateSelector selector) {
        return Type.GetType(info.GetString(nameof(Type.FullName)));
    }
}

// Just a stub, doesn't need an implementation
public class TypeStub : Type { ... }

// Binds "System.RuntimeType" to our TypeStub
public class TypeSerializationBinder : SerializationBinder {
    public override Type BindToType(string assemblyName, string typeName) {
        if(typeName == "System.RuntimeType") {
            return typeof(TypeStub);
        }
        return Type.GetType($"{typeName}, {assemblyName}");
    }
}

// Selected out TypeSerializationSurrogate when [de]serializing Type
public class TypeSurrogateSelector : ISurrogateSelector {
    public virtual void ChainSelector(ISurrogateSelector selector) => throw new NotSupportedException();

    public virtual ISurrogateSelector GetNextSelector() => throw new NotSupportedException();

    public virtual ISerializationSurrogate GetSurrogate(Type type, StreamingContext context, out ISurrogateSelector selector) {
        if(typeof(Type).IsAssignableFrom(type)) {
            selector = this;
            return new TypeSerializationSurrogate();
        }
        selector = null;
        return null;
    }
}

Usage Example:

byte[] bytes
var serializeFormatter = new BinaryFormatter() {
    SurrogateSelector = new TypeSurrogateSelector()
}
using (var stream = new MemoryStream()) {
    serializeFormatter.Serialize(stream, typeof(string));
    bytes = stream.ToArray();
}

var deserializeFormatter = new BinaryFormatter() {
    SurrogateSelector = new TypeSurrogateSelector(),
    Binder = new TypeDeserializationBinder()
}
using (var stream = new MemoryStream(bytes)) {
    type = (Type)deserializeFormatter .Deserialize(stream);
    Assert.Equal(typeof(string), type);
}

Just looked at its definition, it is not marked as Serializable. If you really need this data to be serialize, then you may have to convert it to a custom class that is marked as such.

public abstract class Type : System.Reflection.MemberInfo
    Member of System

Summary:
Represents type declarations: class types, interface types, array types, value types, enumeration types, type parameters, generic type definitions, and open or closed constructed generic types.

Attributes:
[System.Runtime.InteropServices.ClassInterfaceAttribute(0),
System.Runtime.InteropServices.ComDefaultInterfaceAttribute(System.Runtime.InteropServices._Type),
System.Runtime.InteropServices.ComVisibleAttribute(true)]
Related