【问题标题】:InvalidOperationException: The type of the argument object 'Scratch' is not primitiveInvalidOperationException:参数对象“Scratch”的类型不是原始的
【发布时间】:2014-12-10 00:34:22
【问题描述】:

所以有错误。

InvalidOperationException:参数对象'Scratch'的类型不是原始的

我正在做的是序列化一个类列表 (List<BaseEnemy>)。 BaseEnemy 类中还有一个类列表 (List<BaseMoves>)。当我运行东西时,BaseEnemy 的列表会正确序列化。但是,BaseMove 列表没有。 Scratch 是一个派生自 BaseMove 的类,存储在 List<BaseMove> 中。

我遇到的问题。我在这里找到了答案...HERE...

"使用 XmlInclude 属性标记 BaseMove 类传递您的派生类 作为参数:"

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

像魅力一样工作!那我为什么要在这里发这个?新问题。我有 HUNDREDS 来自 BaseMove 的移动。是否有捷径,或者我必须在 XmlInclude 中编写每个“typeof(...)”?

编辑 - 我找到的另一个解决方案。

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();
}

然后在序列化过程中我只是调用这个函数来用作一个额外类型的数组。

Type[] extraTypes = GetAllSubTypes(typeof(BaseMove));
XmlSerializer xml = new XmlSerializer(typeof(List<BaseEnemy>), extraTypes);

【问题讨论】:

    标签: c# xml list unity3d xml-serialization


    【解决方案1】:

    您可以遍历应用程序域中的所有程序集,找到可从您的基本类型分配的所有类型,并在构造 XmlSerializer 时将它们作为已知类型传递。但是,一些警告:

    1. 程序集是按需加载的,因此您不能只执行一次搜索,并且
    2. XmlSerializer 一旦构造完成,就必须缓存在哈希表或字典中并重复使用以防止内存和资源泄漏。详情请见here

    因此,您可以执行以下操作:

    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;
            }
        }
    }
    

    然后这样称呼它:

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

    例如,

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

    更新

    如果序列化程序的参数化通用静态表看起来很奇怪(公平地说,确实如此),您可以将序列化程序存储在非通用全局哈希表中,如 documentation 中所建议的那样:

    如果您使用任何其他构造函数,则会生成同一程序集的多个版本并且永远不会卸载,这会导致内存泄漏和性能下降。最简单的解决方案是使用前面提到的两个构造函数之一。否则,您必须将程序集缓存在 Hashtable 中,如下例所示。

    但是,文档有点掩盖了如何为XmlSerializer 生成密钥。代码示例也不是线程安全的,可能是因为所有这些东西都可以追溯到 .Net 1.0。所以这里有一些逻辑可以在全局哈希表中正确地键入和回收具有已知额外类型的XmlSerializer

    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());
        }
    }
    

    然后以与调用等效的 XmlSerializer 构造函数相同的方式调用它:

        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) })));
        }
    

    【讨论】:

    • 嘿@dbc 我知道这已经有几天了,但我最近有一些空闲时间,我想看看我是否正确地处理了你的建议。在我编辑的原始问题中,添加了到目前为止我写的内容。我在正确的轨道上吗? (哦……我会把所有这些都放在我处理保存和加载的类中吗?)
    • @DavidRosenIII - 你不会这样做public static class XmlSerializerWithKnownTypeCreator&lt;List&lt;BaseMove&gt;&gt;。这是一个泛型静态类,其中泛型参数对应于传递给XmlSerializer 的类型。 (我认为这会比一些全局哈希表更方便。)将其保留为 XmlSerializerWithKnownTypeCreator&lt;T&gt; 然后像 var serializer = XmlSerializerWithKnownTypeCreator&lt;List&lt;BaseMove&gt;&gt;.CreateKnownTypeSerializer&lt;BaseMove&gt;(); 一样使用它
    • 谢谢。 x_x 不知道我在想什么,但这是漫长的一周。我还在stackoverflow上的另一个问题中遇到了一个简洁的小静态函数。我已经把它贴在原帖里了。我实现了它,它工作得很好,但不知何故,我觉得你会说我仍然需要执行哈希表或字典步骤。
    • @DavidRosenIII - 遗憾的是,docs 明确表示“如果您使用任何其他构造函数,则会生成同一程序集的多个版本并且永远不会卸载,这会导致内存泄漏和性能下降. 最简单的解决方案是使用前面提到的两个构造函数之一。否则,您必须将程序集缓存在“哈希表”中。如果通用静态版本看起来很奇怪,我可以推荐一个非通用版本。
    猜你喜欢
    • 1970-01-01
    • 2018-06-22
    • 1970-01-01
    • 2011-12-02
    • 1970-01-01
    • 1970-01-01
    • 2016-03-30
    • 2013-02-02
    • 1970-01-01
    相关资源
    最近更新 更多