【问题标题】:Can I create XmlAttributeOverrides using a custom attribute?我可以使用自定义属性创建 XmlAttributeOverrides 吗?
【发布时间】:2018-06-09 17:24:18
【问题描述】:

我正在尝试在名为GenericSerializable 的自定义属性后面实现 C# 中基本序列化的抽象。本质上,我希望此属性在应用于公共属性时向某些序列化程序(无论是 XML、JSON、Protobuf 等)指示该属性应该被序列化,如果它不存在则不应该被序列化。目前,我可以获得特定属性是否具有该属性的信息,但我正在努力实现 XML 序列化程序。这是我的测试继承结构:

public abstract class SerializableObjectBase
{
    protected int _typeIndicator;

    [GenericSerializable]
    public int TypeIndicator
    {
        get
        {
            return _typeIndicator;
        }
    }

    public SerializableObjectBase()
    {
        _typeIndicator = 0;
    }
}

public class SerializableObjectChildOne : SerializableObjectBase
{
    private int _test;

    public int Test
    {
        get
        {
            return _test;
        }
        set
        {
            _test = value;
        }
    }

    public SerializableObjectChildOne() : base()
    {
        _test = 1234;
        _typeIndicator = 1;
    }
}

public class SerializableObjectChildTwo : SerializableObjectChildOne
{        
    private List<int> _list;

    public List<int> List
    {
        get
        {
            return _list;
        }
    }

    public SerializableObjectChildTwo() : base()
    {
        _list = new List<int>();
        _typeIndicator = 2;
    }
}

我希望此示例的 XML 看起来像:

<?xml version="1.0" encoding="utf-8"?>
<SerializableObjectChildTwo xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema">
  <TypeIndicator>2</TypeIndicator>
</SerializableObjectChildTwo>

但它看起来像这样:

<?xml version="1.0" encoding="utf-8"?>
<SerializableObjectChildTwo xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema">
  <Test>1234</Test>
</SerializableObjectChildTwo>

序列化代码如下:

using (FileStream fs = new FileStream(".\\output.xml", FileMode.Create))
{
    // object to serialize
    SerializableObjectChildTwo s = new SerializableObjectChildTwo();

    XmlAttributeOverrides overrides = new XmlAttributeOverrides();

    // check whether each property has the custom attribute
    foreach (PropertyInfo property in typeof(SerializableObjectChildTwo).GetProperties())
    {
        XmlAttributes attrbs = new XmlAttributes();

        // if it has the attribute, tell the overrides to serialize this property
        if (property.CustomAttributes.Any((attr) => attr.AttributeType.Equals(typeof(GenericSerializable))))
        {
            Console.WriteLine("Adding " + property.Name + "");
            attrbs.XmlElements.Add(new XmlElementAttribute(property.Name));
        }
        else
        {
            // otherwise, ignore the property
            Console.WriteLine("Ignoring " + property.Name + "");
            attrbs.XmlIgnore = true;
        }

        // add this property to the list of overrides
        overrides.Add(typeof(SerializableObjectChildTwo), property.Name, attrbs);
    }

    // create the serializer
    XmlSerializer xml = new XmlSerializer(typeof(SerializableObjectChildTwo), overrides);

    // serialize it
    using (TextWriter tw = new StreamWriter(fs))
    {
        xml.Serialize(tw, s);
    }
}

有趣的是,如果我将GenericSerializable 属性添加到SerializableObjectChildTwo 中的List 属性,它会按预期运行。问题是,由于某种原因,尽管我添加了 attrbs.XmlIgnore = trueTest 仍在序列化,尽管我将其明确添加到 XmlAttributeOverridesTypeIndicator 仍未序列化。

我是否错误地使用了覆盖?我不需要任何花哨的 XML 模式或任何东西,我只想根据我的自定义属性的存在与否来序列化/不序列化公共属性。

提前致谢。

【问题讨论】:

标签: c# .net xml serialization xmlserializer


【解决方案1】:

这里有几个问题:

  1. 使用XmlAttributeOverrides.Add (Type, String, XmlAttributes) 为属性添加覆盖时,传入的type 必须是属性的声明类型,而不是要序列化的派生类型。

    例如要在序列化 SerializableObjectChildTwo 时抑制 Testtype 必须是 SerializableObjectChildOne

  2. 属性TypeIndicator 未序列化,因为它没有公共设置器。正如 Why are properties without a setter not serialized 中所述,在大多数情况下,成员必须是公开可读和可写的,才能使用 XmlSerializer 进行序列化。

  3. 话虽如此,仅获取集合属性可以XmlSerializer 序列化。 Introducing XML Serialization:

    对此进行了解释,尽管不清楚

    XML 序列化不会转换方法、索引器、私有字段或只读属性(只读集合除外)。要序列化对象的所有字段和属性,包括公共的和私有的,请使用 DataContractSerializer 而不是 XML 序列化。

    (这里的只读集合实际上是指只读的、预分配的集合属性。)

    这解释了为什么 List 属性尽管是 get-only 却会被序列化。

  4. 您应该缓存序列化程序以避免内存泄漏,如 Memory Leak using StreamReader and XmlSerializer 中所述。

将所有这些放在一起,您可以使用以下扩展方法为SerializableObjectChildTwo 构造一个序列化程序:

public static class SerializableObjectBaseExtensions
{
    static readonly Dictionary<Type, XmlSerializer> serializers = new Dictionary<Type, XmlSerializer>();
    static readonly object padlock = new object();

    public static XmlSerializer GetSerializer<TSerializable>(TSerializable obj) where TSerializable : SerializableObjectBase, new()
    {
        return GetSerializer(obj == null ? typeof(TSerializable) : obj.GetType());
    }

    public static XmlSerializer GetSerializer<TSerializable>() where TSerializable : SerializableObjectBase, new()
    {
        return GetSerializer(typeof(TSerializable));
    }

    static XmlSerializer GetSerializer(Type serializableType)
    {
        lock (padlock)
        {
            XmlSerializer serializer;
            if (!serializers.TryGetValue(serializableType, out serializer))
                serializer = serializers[serializableType] = CreateSerializer(serializableType);
            return serializer;
        }
    }

    static XmlSerializer CreateSerializer(Type serializableType)
    {
        XmlAttributeOverrides overrides = new XmlAttributeOverrides();

        for (var declaringType = serializableType; declaringType != null && declaringType != typeof(object); declaringType = declaringType.BaseType)
        {
            // check whether each property has the custom attribute
            foreach (PropertyInfo property in declaringType.GetProperties(BindingFlags.Public | BindingFlags.DeclaredOnly | BindingFlags.Instance))
            {
                XmlAttributes attrbs = new XmlAttributes();

                // if it has the attribute, tell the overrides to serialize this property
                // property.IsDefined is faster than actually creating and returning the attribute
                if (property.IsDefined(typeof(GenericSerializableAttribute), true))
                {
                    Console.WriteLine("Adding " + property.Name + "");
                    attrbs.XmlElements.Add(new XmlElementAttribute(property.Name));
                }
                else
                {
                    // otherwise, ignore the property
                    Console.WriteLine("Ignoring " + property.Name + "");
                    attrbs.XmlIgnore = true;
                }

                // add this property to the list of overrides
                overrides.Add(declaringType, property.Name, attrbs);
            }
        }

        // create the serializer
        return new XmlSerializer(serializableType, overrides);
    }
}

工作 .Net 小提琴 here.

【讨论】:

    【解决方案2】:

    我找到了一个按预期工作的解决方案。

    这一行:

     overrides.Add(typeof(SerializableObjectChildTwo), property.Name, attrbs);
    

    应该是:

     overrides.Add(property.DeclaringType, property.Name, attrbs);
    

    区别在于作为第一个参数提供的类型。感谢@dbc 为我指明了正确的方向。

    【讨论】:

      猜你喜欢
      • 2019-01-08
      • 1970-01-01
      • 2013-04-03
      • 2021-07-15
      • 2021-10-05
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2021-01-13
      相关资源
      最近更新 更多