16

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

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

我需要它来支持“版本”,所以当我稍后在代码中更改默认值时 - 序列化属性获取它已经序列化的值,而不是“最新”的值。我可以考虑以下几点:

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

但是我有可能会错过一些秘密属性,它可以让我在没有太多痛苦的情况下做到这一点=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以粗体文本显示所有值。

4

4 回答 4

7

只要您不需要 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>
于 2013-03-28T13:10:58.367 回答
4

您可以使用两个属性:

// 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();
    }
}
于 2013-04-01T22:44:41.040 回答
3

我相信您正在寻找的是ShouldSerialize()andReset()。使用这些将进一步扩展您的类(每个属性有两个函数),但是,它专门实现了您正在寻找的东西。

这是一个简单的例子:

// 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()andReset()函数一起使用(您只能使用 or)。


编辑:ShouldSerialize()为功能添加说明。

因为目前没有办法序列化一个默认值并让 PropertyGrid 知道一个属性有它的默认值,所以您必须实现一个条件来检查您是否处于设计模式。

假设您的类从Componentor派生Control,您有一个属性,该属性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属性设置为,之后取消设置。true XmlSerializer.Serialize

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

于 2013-03-27T17:20:38.727 回答
1

XmlSerializer可以用 XmlAttributeOverrides覆盖 can的这种行为

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

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))
于 2014-11-12T18:05:03.010 回答