Welcome to OGeek Q&A Community for programmer and developer-Open, Learning and Share
Welcome To Ask or Share your Answers For Others

Categories

0 votes
616 views
in Technique[技术] by (71.8m points)

c# - InvalidOperationException: The type of the argument object 'Scratch' is not primitive

So there's the error.

InvalidOperationException: The type of the argument object 'Scratch' is not primitive

What I'm doing is serializing a list of classes (List<BaseEnemy>). The BaseEnemy class also has a list in it of classes (List<BaseMoves>). When I run things, the list of BaseEnemy serializes properly. However, the list of BaseMoves does not. Scratch is a class that derives from BaseMove that is stored in the List<BaseMove>.

That WAS the problem that I was having. I found the answer on here...HERE...

"Mark BaseMove class with XmlInclude attribute passing your derived class as parameter:"

[XmlInclude(typeof(Scratch))]
public class BaseMove
{
    public BaseMove()
    {
    }
}

Works like a charm! So why am I posting this here? New question. I have HUNDREDS of moves that derive from BaseMove. Is there a shortcut or do I have to write every single "typeof(...)" in the XmlInclude?

EDIT - Another solution I found.

public static Type[] GetAllSubTypes(Type aBaseClass)
{
    List<Type> result = new List<Type>();
    Assembly[] assemblies = System.AppDomain.CurrentDomain.GetAssemblies();
    foreach (Assembly a in assemblies)
    {
        Type[] types = a.GetTypes();
        foreach (Type t in types)
        {
            if (t.IsSubclassOf(aBaseClass))
                result.Add(t);
        }
    }
    return result.ToArray();
}

And then during the serialization I just call this function to use as an Array of extraTypes.

Type[] extraTypes = GetAllSubTypes(typeof(BaseMove));
XmlSerializer xml = new XmlSerializer(typeof(List<BaseEnemy>), extraTypes);
See Question&Answers more detail:os

与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
Welcome To Ask or Share your Answers For Others

1 Reply

0 votes
by (71.8m points)

You could iterate through all the assemblies in your app domain, find all types assignable from your base type, and pass them as known types when constructing your XmlSerializer. However, some caveats:

  1. Assemblies are loaded on demand so you cannot perform the search just once, and
  2. The XmlSerializer, once constructed, must be cached in a hash table or dictionary and reused to prevent memory and resource leaks. See here for details.

Thus, you could do something like:

public static class XmlSerializerWithKnownTypeCreator<T>
{
    static Dictionary<HashSet<Type>, XmlSerializer> table = new Dictionary<HashSet<Type>, XmlSerializer>(HashSet<Type>.CreateSetComparer());

    public static XmlSerializer CreateKnownTypeSerializer<TRoot>()
    {
        return CreateKnownTypeSerializer(new Type [] {typeof(TRoot)});
    }

    public static XmlSerializer CreateKnownTypeSerializer(IEnumerable<Type> baseTypes)
    {
        var set = new HashSet<Type>(
            AppDomain.CurrentDomain.GetAssemblies()
            .SelectMany(a => a.GetTypes())
            .Where(t => baseTypes.Any(baseType => baseType.IsAssignableFrom(t))));
        lock (table)
        {
            XmlSerializer serializer;
            if (table.TryGetValue(set, out serializer))
                return serializer;

            table[set] = serializer = new XmlSerializer(typeof(T), set.ToArray());
            return serializer;
        }
    }
}

And call it like:

var serializer = XmlSerializerWithKnownTypeCreator<DocumentRoot>.CreateKnownTypeSerializer<BaseMove>();

For instance,

var serializer = XmlSerializerWithKnownTypeCreator<List<BaseClass>>.CreateKnownTypeSerializer<BaseMove>();

Update

If the parameterized generic static table of serializers looks weird (and to be fair, it kind of does), you can store the serializers in a non-generic global hash table, as is suggested in the documentation:

If you use any of the other constructors, multiple versions of the same assembly are generated and never unloaded, which results in a memory leak and poor performance. The easiest solution is to use one of the previously mentioned two constructors. Otherwise, you must cache the assemblies in a Hashtable, as shown in the following example.

However, the doc sort of glosses over how to generate a key for the XmlSerializer. The code sample also isn't thread-safe, perhaps since all this stuff dates back to .Net 1.0. So here's some logic that will correctly key and recycle an XmlSerializer with known extra types, in a global hash table:

public abstract class XmlserializerKey
{
    readonly Type serializerType;

    public XmlserializerKey(Type serializerType)
    {
        this.serializerType = serializerType;
    }

    protected Type SerializerType { get { return serializerType; } }

    public override bool Equals(object obj)
    {
        if (ReferenceEquals(this, obj))
            return true;
        else if (ReferenceEquals(null, obj))
            return false;
        if (GetType() != obj.GetType())
            return false;
        XmlserializerKey other = (XmlserializerKey)obj;
        if (other.serializerType != serializerType)
            return false;
        return true;
    }

    public override int GetHashCode()
    {
        int code = 0;
        if (serializerType != null)
            code ^= serializerType.GetHashCode();
        return code;
    }

    public override string ToString()
    {
        return string.Format("Serializer type: " + serializerType.ToString());
    }
}

public abstract class XmlserializerKeyWithExtraTypes : XmlserializerKey
{
    static IEqualityComparer<HashSet<Type>> comparer;

    readonly HashSet<Type> moreTypes = new HashSet<Type>();

    static XmlserializerKeyWithExtraTypes()
    {
        comparer = HashSet<Type>.CreateSetComparer();
    }

    public XmlserializerKeyWithExtraTypes(Type serializerType, IEnumerable<Type> extraTypes)
        : base(serializerType)
    {
        if (extraTypes != null)
            foreach (var type in extraTypes)
                moreTypes.Add(type);
    }

    protected Type[] MoreTypes { get { return moreTypes.ToArray(); } }

    public override bool Equals(object obj)
    {
        if (!base.Equals(obj))
            return false;
        XmlserializerKeyWithExtraTypes other = (XmlserializerKeyWithExtraTypes)obj;
        return comparer.Equals(moreTypes, other.moreTypes);
    }

    public override int GetHashCode()
    {
        int code = base.GetHashCode();
        if (moreTypes != null)
            code ^= comparer.GetHashCode(moreTypes);
        return code;
    }
}

public sealed class XmlSerializerKeyWithKnownTypes : XmlserializerKeyWithExtraTypes
{
    public XmlSerializerKeyWithKnownTypes(Type serializerType, IEnumerable<Type> otherTypes)
        : base(serializerType, otherTypes)
    {
    }

    public XmlSerializer CreateSerializer()
    {
        return new XmlSerializer(SerializerType, MoreTypes);
    }
}

public static class XmlSerializerHashTable
{
    static Dictionary<object, XmlSerializer> dict;

    static XmlSerializerHashTable()
    {
        dict = new Dictionary<object, XmlSerializer>();
    }

    public static XmlSerializer DemandSerializer(object key, Func<object, XmlSerializer> factory)
    {
        lock (dict)
        {
            XmlSerializer value;
            if (!dict.TryGetValue(key, out value))
                dict[key] = value = factory(key);
            return value;
        }
    }
}

public static class XmlSerializerWithKnownDerivedTypesCreator
{
    public static XmlSerializer CreateKnownTypeSerializer(Type type, IEnumerable<Type> extraTypes)
    {
        var allExtraTypes = 
            AppDomain.CurrentDomain.GetAssemblies()
            .SelectMany(a => a.GetTypes())
            .Where(t => extraTypes.Any(extraType => extraType.IsAssignableFrom(t)));
        var key = new XmlSerializerKeyWithKnownTypes(type, allExtraTypes);
        return XmlSerializerHashTable.DemandSerializer(key, k => ((XmlSerializerKeyWithKnownTypes)k).CreateSerializer());
    }
}

And then call it in the same way as you would call the equivalent XmlSerializer constructor:

    public static void Test2()
    {
        List<BaseClass> list = new List<BaseClass>();
        list.Add(new BaseClass());
        list.Add(new MidClass());
        list.Add(new DerivedClass1());
        list.Add(new DerivedClass2());

        var serializer = XmlSerializerWithKnownDerivedTypesCreator.CreateKnownTypeSerializer(list.GetType(), new Type[] { typeof(BaseClass) });
        string xml = XmlSerializationHelper.GetXml(list, serializer, false);
        Debug.WriteLine(xml);

        // No assert below:
        Debug.Assert(object.ReferenceEquals(serializer, XmlSerializerWithKnownDerivedTypesCreator.CreateKnownTypeSerializer(list.GetType(), new Type[] { typeof(BaseClass) })));
    }

与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
OGeek|极客中国-欢迎来到极客的世界,一个免费开放的程序员编程交流平台!开放,进步,分享!让技术改变生活,让极客改变未来! Welcome to OGeek Q&A Community for programmer and developer-Open, Learning and Share
Click Here to Ask a Question

...