Как рассказать XmlSerializer о сериализации свойств с помощью [DefautValue (...)] всегда? - программирование
Подтвердить что ты не робот

Как рассказать XmlSerializer о сериализации свойств с помощью [DefautValue (...)] всегда?

Я использую атрибут 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 будет NOT помещать свойства со значением по умолчанию в xml файл, и это BAD (и я пытаюсь его исправить).

Если я раскомментирую (* 2) и (* 3), то он полностью совпадает с раскомментированием (* 1).

Если я раскомментирую (* 2) и (* 4), то XmlSerializer всегда будет помещать свойства в xml файл, но это происходит потому, что они больше не имеют значения по умолчанию и PropertyGrid показывает все значения жирным шрифтом.

4b9b3361

Ответ 1

Пока вам не нужны атрибуты в вашем 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>

Ответ 2

Вы можете использовать два свойства:

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

Ответ 3

Я считаю, что вы ищете 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() (вы используете только или).


Изменить: Добавление разъяснений для функции ShouldSerialize().

Поскольку в настоящее время существует нет способ сериализации значения по умолчанию, и пусть PropertyGrid знает, что свойство имеет значение по умолчанию, вы должны выполнить условие, проверяющее, находитесь ли вы в режиме разработки.

Предполагая, что ваш класс получен из Component или 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).

Ответ 4

Такое поведение параметра 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))