如何告诉XmlSerializer总是用[defaultvalue(..)]序列化属性

本文关键字:序列化 属性 defaultvalue XmlSerializer 何告诉 | 更新日期: 2023-09-27 18:03:41

我使用DefaultValue属性来显示正确的PropertyGrid行为(它以粗体显示与默认值不同的值)。现在,如果我想用XmlSerializer序列化显示的对象,那么xml文件中将没有带有默认值的属性条目。

告诉XmlSerializer序列化这些数据的最简单方法是什么?

我需要它来支持"版本",所以当我稍后在代码序列化属性中更改默认值时,它会获得序列化的值,而不是"最新"的值。我可以考虑如下:

  • 覆盖PropertyGrid的行为(使用自定义属性,因此XmlSerializer将忽略它);
  • 做自定义xml序列化,其中忽略DefaultValue;
  • 在传递给XmlSeriazer之前做一些事情,这样它就不会再包含DefaultValue了。

但有可能我错过了一些秘密属性,这些属性可以让我轻松地完成它= d

这是我想要的一个例子:

    private bool _allowNegative = false;
    /// <summary>
    /// Get or set if negative results are allowed
    /// </summary>
    [Category(CategoryAnalyse)]
    [Admin]
    [TypeConverter(typeof(ConverterBoolOnOff))]
    //[DefaultValue(false)] *1
    public bool AllowNegative
    {
        get { return _allowNegative; }
        set
        {
            _allowNegative = value;
            ConfigBase.OnConfigChanged();
        }
    }
    //public void ResetAllowNegative() { _allowNegative = false; } *2
    //public bool ShouldSerializeAllowNegative() { return _allowNegative; } *3
    //public bool ShouldSerializeAllowNegative() { return true; } *4

如果我取消注释(*1),那么我在PropertyGrid中有预期的效果-具有默认值的属性以正常文本显示,否则文本为粗体。然而,XmlSerializer将属性与默认值放入xml文件,这是(我正试图修复它)。

如果我取消注释(*2)和(*3),那么它与取消注释(*1)完全相同。

如果我取消注释(*2)和(*4),那么XmlSerializer将始终将属性放入xml文件,但这是因为它们不再具有默认值PropertyGrid以粗体文本显示所有值

如何告诉XmlSerializer总是用[defaultvalue(..)]序列化属性

只要您的Xml中不需要属性,如果您使用DataContractSerializer来代替,您将得到您想要的行为。

[DataContract]
public class Test
{
    [DataMember]
    [DefaultValue(false)]
    public bool AllowNegative { get; set; }
}
void Main()
{
    var sb2 = new StringBuilder();
    var dcs = new DataContractSerializer(typeof(Test));
    using(var writer = XmlWriter.Create(sb2))
    {
        dcs.WriteObject(writer, new Test());
    }
    Console.WriteLine(sb2.ToString());  
}

产生(减去名称空间等)

<Test>
    <AllowNegative>false</AllowNegative>
</Test>

您可以使用两个属性:

// For property grid only:
[Category(CategoryAnalyse)]
[TypeConverter(typeof(ConverterBoolOnOff))]
[DefaultValue(false)]
[XmlIgnore]
public bool AllowNegative
{
    get { return _allowNegative; }
    set
    {
        _allowNegative = value;
        ConfigBase.OnConfigChanged();
    }
}
// For serialization:
[Browsable(false)]
[EditorBrowsable(EditorBrowsableState.Never)]
[TypeConverter(typeof(ConverterBoolOnOff))]
[XmlElement("AllowNegative")]
public bool AllowNegative_XML
{
    get { return _allowNegative; }
    set
    {
        _allowNegative = value;
        ConfigBase.OnConfigChanged();
    }
}

我相信你正在寻找的是ShouldSerialize()Reset()。使用这些属性将进一步扩展您的类(每个属性有两个函数),但是,它实现了您正在寻找的具体目标。

下面是一个简单的例子:

// your property variable
private const String MyPropertyDefault = "MyValue";
private String _MyProperty = MyPropertyDefault;
// your property
// [DefaultValueAttribute("MyValue")] - cannot use DefaultValue AND ShouldSerialize()/Reset()
public String MyProperty
{
    get { return _MyProperty; }
    set { _MyProperty = value; }
}

// IMPORTANT!
// notice that the function name is "ShouldSerialize..." followed
// by the exact (!) same name as your property
public Boolean ShouldSerializeMyProperty()
{
    // here you would normally do your own comparison and return true/false
    // based on whether the property should be serialized, however,
    // in your case, you want to always return true when serializing!
    // IMPORTANT CONDITIONAL STATEMENT!
    if (!DesignMode)
        return true; // always return true outside of design mode (is used for serializing only)
    else
        return _MyProperty != MyPropertyDefault; // during design mode, we actually compare against the default value
}
public void ResetMyProperty()
{
    _MyProperty = MyPropertyDefault;
}

注意,因为您希望保持PropertyGrid功能不变,所以在调用ShouldSerialize()函数时,您必须知道是否正在序列化。我建议在序列化时实现某种控制标志,因此总是return true


请注意,不能DefaultValue属性与ShouldSerialize()Reset()函数结合使用(只能使用或)。


Edit:添加ShouldSerialize()函数的说明。

由于目前没有方法来序列化默认值并让PropertyGrid知道属性有其默认值,因此必须实现一个条件检查是否处于设计模式。

假设你的类来自ComponentControl,你有一个DesignMode属性,它是由Visual Studio在设计时设置的,只有。条件如下:

if (!DesignMode)
    return true; // always return true outside of design mode (is used for serializing only)
else
    return _MyProperty != MyPropertyDefault; // during design mode, we actually compare against the default value

编辑2:我们不是在谈论Visual Studio的设计模式。

根据上面的代码,创建另一个名为IsSerializing的属性。IsSerializing属性在调用XmlSerializer.Serialize之前设置为true ,在之后取消设置。

最后,将if (!DesignMode)条件语句修改为if (IsSerializing)

XmlSerializer的这种行为可以被覆盖XmlAttributeOverrides

我从这里借用了这个想法:

static public XmlAttributeOverrides GetDefaultValuesOverrides(Type type)
{
    XmlAttributeOverrides explicitOverrides = new XmlAttributeOverrides();
    PropertyDescriptorCollection c = TypeDescriptor.GetProperties(type);
    foreach (PropertyDescriptor p in c)
    {
        AttributeCollection attributes = p.Attributes;
        DefaultValueAttribute defaultValue = (DefaultValueAttribute)attributes[typeof(DefaultValueAttribute)];
        XmlIgnoreAttribute noXML = (XmlIgnoreAttribute)attributes[typeof(XmlIgnoreAttribute)];
        XmlAttributeAttribute attribute = (XmlAttributeAttribute)attributes[typeof(XmlAttributeAttribute)];
        if ( defaultValue != null && noXML == null )
        {
            XmlAttributeAttribute xmlAttribute = new XmlAttributeAttribute(attribute.AttributeName);
            XmlAttributes xmlAttributes = new XmlAttributes();
            xmlAttributes.XmlAttribute = xmlAttribute;
            explicitOverrides.Add(userType, attribute.AttributeName, xmlAttributes);
        }
    }
    return explicitOverrides;
}

并使我的自我一个属性来装饰应该发出默认值的类。如果你想对所有类都这样做,我相信你可以调整整个概念。

Public Class EmitDefaultValuesAttribute
    Inherits Attribute
    Private Shared mCache As New Dictionary(Of Assembly, XmlAttributeOverrides)
    Public Shared Function GetOverrides(assembly As Assembly) As XmlAttributeOverrides
        If mCache.ContainsKey(assembly) Then Return mCache(assembly)
        Dim xmlOverrides As New XmlAttributeOverrides
        For Each t In assembly.GetTypes()
            If t.GetCustomAttributes(GetType(EmitDefaultValuesAttribute), True).Count > 0 Then
                AddOverride(t, xmlOverrides)
            End If
        Next
        mCache.Add(assembly, xmlOverrides)
        Return xmlOverrides
    End Function
    Private Shared Sub AddOverride(t As Type, xmlOverrides As XmlAttributeOverrides)
        For Each prop In t.GetProperties()
            Dim defaultAttr = prop.GetCustomAttributes(GetType(DefaultValueAttribute), True).FirstOrDefault()
            Dim xmlAttr As XmlAttributeAttribute = prop.GetCustomAttributes(GetType(XmlAttributeAttribute), True).FirstOrDefault()
            If defaultAttr IsNot Nothing AndAlso xmlAttr IsNot Nothing Then
                Dim attrs As New XmlAttributes '= {New XmlAttributeAttribute}
                attrs.XmlAttribute = xmlAttr
                ''overide.Add(t, xmlAttr.AttributeName, attrs)
                xmlOverrides.Add(t, prop.Name, attrs)
            End If
        Next
    End Sub

因为xsd.exe产生部分类,你可以在单独的文件中添加这个EmitDefaultValuesAttribute:

<EmitDefaultValuesAttribute()>
Public MyClass
    Public Property SubClass() As MySubClass
End Class
<EmitDefaultValuesAttribute()>
Public MySubClass
End Class

用法如下:

Dim serializer As New XmlSerializer(GetType(MyClass), EmitDefaultValuesAttribute.GetOverrides(GetType(MyClass).Assembly))