【问题标题】:How to make XmlSerializer.Deserialize handle DefaultAttribute properly?如何使 XmlSerializer.Deserialize 正确处理 DefaultAttribute?
【发布时间】:2013-08-28 21:25:07
【问题描述】:

Microsoft XmlSerializer 中似乎存在错误/不一致:如果您的属性标记为 System.ComponentModel.DefaultValue 属性,则不会序列化。很公平 - 这可以被视为一种预期的行为。

问题是反序列化时不尊重相同的属性。下面的代码说明了这个问题。

问题是我怎样才能绕过这个?我可能有数百个业务类在 UI 层(视图)中使用了默认值,因此构造函数中的默认值初始化不是一个选项。它必须是通用的。我可以创建一个全新的默认属性,但这似乎是重复的工作。您是否看到了覆盖 XmlSerializer 行为的方法,或者我应该只使用另一个可以更好地完成工作的序列化程序?

示例代码:

public class DefaultValueTestClass
{
    [System.ComponentModel.DefaultValue(10000)]
    public int Foo { get; set; }
}

[TestMethod]
public void SimpleDefaultValueTest()
{
    // Create object and set the property value TO THE DEFAULT
    var before = new DefaultValueTestClass();
    before.Foo = 10000;
    // Serialize => xml
    var serializer = new System.Xml.Serialization.XmlSerializer(typeof(DefaultValueTestClass));
    string xml;
    using (var stream = new System.IO.StringWriter())
    {
        serializer.Serialize(stream, before);
        xml = stream.ToString();
    }

    // Deserialize the same object
    DefaultValueTestClass after;
    using (var reader = new System.IO.StringReader(xml))
    {
        after = (DefaultValueTestClass)serializer.Deserialize(reader);
    }

    // before.Foo = 10000
    // after.Foo = 0
    Assert.AreEqual(before.Foo, after.Foo);
}

【问题讨论】:

  • 我猜你可以通过反射,在反序列化时,使用 DefaultValue 属性获取你的类及其超类的所有属性,为每个属性获取值,将其转换为正确的类型,然后仍然通过反射,在你的反序列化对象上调用属性的setter方法,并将抓取的值作为参数
  • @jbl 是的,这就是我在 UI 层所做的。但是我不能在 XmlSerializer.Deserialize 之后这样做,因为我不知道 Foo=0 是因为它没有被序列化还是因为它被用户设置为 0。这样,我需要在反序列化之前插入序列化程序发生。并且不知道如何(除了在构造函数中,由于课程数量众多,我不想这样做)。
  • 也许这可以通过一些动态代理来完成,这些代理将实现 IXmlSerializable 接口来代替您的类。我必须承认这只是随机的想法......

标签: c# serialization xmlserializer


【解决方案1】:

实现默认值是你的工作[DefaultValue] 只是说“这是默认设置,您无需担心这一点”——它没有应用它。这不仅适用于XmlSerializer,还适用于[DefaultValue] 所属的核心System.ComponentModel API(它驱动PropertyGrid 中的粗体/非粗体等)

基本上:

public class DefaultValueTestClass
{
    public DefaultValueTestClass()
    {
        Foo = 10000;
    }
    [DefaultValue(10000)]
    public int Foo { get; set; }
}

将以您期望的方式工作。如果你想让它序列化它是否是那个特定的值,那么正确的实现是:

public class DefaultValueTestClass
{
    public DefaultValueTestClass()
    {
        Foo = 10000;
    }
    public int Foo { get; set; }
}

如果您想保留[DefaultValue],但希望它始终序列化,那么:

public class DefaultValueTestClass
{
    [DefaultValue(10000)]
    public int Foo { get; set; }

    [Browsable(false), EditorBrowsable(EditorBrowsableState.Never)]
    public bool ShouldSerializeFoo() { return true; }
}

其中ShouldSerialize*System.ComponentModel 中的另一个模式,可被多个序列化程序识别。


这里有一些 UI 代码表明 XmlSerializer 实际上所做的事情与 UI 代码(基于 System.ComponentModel)一直所做的完全相同:

using System.ComponentModel;
using System.Windows.Forms;
static class Program
{
    [System.STAThread]
    static void Main()
    {
        Application.EnableVisualStyles();
        using (var form = new Form())
        using (var grid = new PropertyGrid())
        {
            grid.Dock = DockStyle.Fill;
            var obj = new DefaultValueTestClass
            {   // TODO - try with other numbers to
                // see bold / not bold
                Foo = 10000
            };
            // note in the grid the value is shown not-bold; that is
            // because System.ComponentModel is saying
            // "this property doesn't need to be serialized"
            // - or to show it more explicitly:
            var prop = TypeDescriptor.GetProperties(obj)["Foo"];
            bool shouldSerialize = prop.ShouldSerializeValue(obj);
            // ^^^ false, because of the DefaultValueAttribute
            form.Text = shouldSerialize.ToString(); // win title

            grid.SelectedObject = obj;
            form.Controls.Add(grid);
            Application.Run(form);            
        }
    }
}

public class DefaultValueTestClass
{
    [System.ComponentModel.DefaultValue(10000)]
    public int Foo { get; set; }
}

【讨论】:

  • 正如我上面所说:“我可能有数百个业务类在 UI 层(视图)中使用默认值,因此在构造函数中初始化默认值不是一个选项。”我在 UI 中使用属性,就像在旧 WinForms 时代所做的属性网格等一样。
  • @Ope 你看到我在底部编辑的第三个选项没有使用这个吗?另外:XmlSerializer 使用的规则与 PropertyGrid 等在 WinForms 中使用的规则完全相同:所以(我并不是贬低这个意思——只是明确说明当前状态是什么)你是什么实际上 i> 说“我多年来一直在错误地使用它,但现在我一直在错误地使用它很重要”——这并不是XmlSerializer 的错。希望ShouldSerialize* 足以让您工作。
  • @Ope 我将编辑 UI 代码以完全相同的方式工作的插图
  • 好的,我确实纠正了:这是设计使然。从 DefaultValueAttribute 文档中并不清楚,但 PropertyDescriptor.ShouldSerializeValue 非常清楚地说明了意图。就我而言,最有意义的是创建一个新属性,例如UserDefault 因为即使它涉及很多类,它也是非常简单的重构 - 与 ShouldSerializeFoo() 相比,未来对象中出错的机会更少。它还更好地显示了意图:这是用户(不是构造函数)创建新对象时的默认值。不过谢谢!
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2016-04-09
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多