Сериализация XAML открытых свойств get / private set - PullRequest
2 голосов
/ 29 ноября 2011

Я использую метод System.Xaml.XamlServices.Save для сериализации объекта, который имеет свойства с общедоступными геттерами / приватными сеттерами, и по своему дизайну эти свойства игнорируются. Я попытался реализовать совет о том, как переопределить привязки XAML по умолчанию и сериализовать частные свойства, но по какой-то причине это не работает - эти свойства все еще игнорируются. Кто-нибудь может указать, что не так:

public class CustomXamlSchemaContext : XamlSchemaContext
{
    protected override XamlType GetXamlType(string xamlNamespace, string name, params XamlType[] typeArguments)
    {
        var type = base.GetXamlType(xamlNamespace, name, typeArguments);
        return new CustomXamlType(type.UnderlyingType, type.SchemaContext, type.Invoker);
    }
}

public class CustomXamlType : XamlType
{
    public CustomXamlType(Type underlyingType, XamlSchemaContext schemaContext, XamlTypeInvoker invoker) : base(underlyingType, schemaContext, invoker)
    {
    }

    protected override bool LookupIsConstructible()
    {
        return true;
    }

    protected override XamlMember LookupMember(string name, bool skipReadOnlyCheck)
    {
        var member = base.LookupMember(name, skipReadOnlyCheck);
        return new CustomXamlMember(member.Name, member.DeclaringType, member.IsAttachable);
    }
}

public class CustomXamlMember : XamlMember
{
    public CustomXamlMember(string name, XamlType declaringType, bool isAttachable) : base(name, declaringType, isAttachable)
    {
    }

    protected override bool LookupIsReadOnly()
    {
        return false;
    }
}


    public static string Save(object instance)
    {
        var stringWriter1 = new StringWriter(CultureInfo.CurrentCulture);
        var stringWriter2 = stringWriter1;
        var settings = new XmlWriterSettings { Indent = true, OmitXmlDeclaration = true };
        using (var writer = XmlWriter.Create(stringWriter2, settings))
        {
            Save(writer, instance);
        }
        return stringWriter1.ToString();
    }

    public static void Save(XmlWriter writer, object instance)
    {
        if (writer == null)
            throw new ArgumentNullException("writer");
        using (var xamlXmlWriter = new XamlXmlWriter(writer, new CustomXamlSchemaContext()))
        {
            XamlServices.Save(xamlXmlWriter, instance);
        }
    }

Имея вышеуказанный код инфраструктуры и класс

public class Class1
{
    public string Property1 { get; private set; }
    public string Property2 { get; set; }
    public DateTime AddedProperty { get; set; }
}

и сериализация экземпляра этого класса с

var obj = new Class1 { Property1 = "value1", Property2 = "value2" };
var objString = Save(obj);

Я получаю результат

<Class1 AddedProperty="0001-01-01" Property2="value2" xmlns="clr-namespace:TestNamespace;assembly=Tests" />

там, где нет записи для Property1.

Что еще более интересно, ни одна из перегрузок не вызывается во время сериализации.

Ответы [ 3 ]

1 голос
/ 01 декабря 2011

Проблема здесь в том, что вы пытаетесь писать только для чтения и частные свойства.

В соответствии со стандартом XAML синтаксически правильные свойства только для чтения - для List, Dictionary и статических членов:

3.3.1.6. Только список, словарь или статические члены могут быть доступны только для чтения Если ни [тип значения] [является списком], ни [типом значения] [не является словарем], ни [является статическим] является Истиной, [только для чтения] ДОЛЖНО быть Ложным.

Посмотрите здесь для подробностей синтаксиса MSDN.

А сам стандарт можно скачать здесь .

Вы также заметите, что только публичные свойства имеют какое-либо значение здесь (из msdn, связанного выше):

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

Для событий WPF XAML - событие, на которое ссылается атрибут имя должно быть публичным и иметь публичного делегата.

Свойство или событие должно быть членом класса или структуры, которые создается экземпляром содержащего объекта элемента.

и если вы подумаете об этом, вы поймете, почему.

Весь стандарт C # действительно построен на использовании классов, которые взаимодействуют с помощью открытых свойств и методов. При этом другим классам не нужно знать, что находится внутри класса за их пределами. Каждый класс можно рассматривать как черный ящик, в котором открытые свойства и методы являются интерфейсом класса для другого кода.

Вот информативный блог о сериализации XAML.

Лично я хотел бы спросить себя, почему мне нужно сериализовать / десериализовать свойства закрытого члена.

1 голос
/ 12 декабря 2011

Оказалось, пара твиков к моему исходному коду решает проблему. Вот окончательное решение:

private class CustomXamlSchemaContext : XamlSchemaContext
{
    public override XamlType GetXamlType(Type type)
    {
        var xamlType = base.GetXamlType(type);
        return new CustomXamlType(xamlType.UnderlyingType, xamlType.SchemaContext, xamlType.Invoker);
    }
}

private class CustomXamlType : XamlType
{
    public CustomXamlType(Type underlyingType, XamlSchemaContext schemaContext, XamlTypeInvoker invoker)
        : base(underlyingType, schemaContext, invoker)
    {
    }

    protected override bool LookupIsConstructible()
    {
        return true;
    }

    protected override XamlMember LookupMember(string name, bool skipReadOnlyCheck)
    {
        var member = base.LookupMember(name, skipReadOnlyCheck);
        return member == null ? null : new CustomXamlMember((PropertyInfo)member.UnderlyingMember, SchemaContext, member.Invoker);
    }

    protected override IEnumerable<XamlMember> LookupAllMembers()
    {
        foreach (var member in base.LookupAllMembers())
        {
            var value = new CustomXamlMember((PropertyInfo)member.UnderlyingMember, SchemaContext, member.Invoker);
            yield return value;
        }
    }

    protected override bool LookupIsPublic()
    {
        return true;
    }
}

private class CustomXamlMember : XamlMember
{
    public CustomXamlMember(PropertyInfo propertyInfo, XamlSchemaContext schemaContext, XamlMemberInvoker invoker)
        : base(propertyInfo, schemaContext, invoker)
    {
    }

    protected override bool LookupIsReadOnly()
    {
        return false;
    }

    protected override bool LookupIsWritePublic()
    {
        return true;
    }
}

Эта настройка позволяет сериализовать / десериализовать свойства с помощью общедоступных методов получения и общедоступных / внутренних / защищенных / частных установщиков. Он игнорирует все остальные свойства. Он также сериализует экземпляры внутренних классов.

0 голосов
/ 29 ноября 2011

Я не совсем уверен, что не так с приведенным выше кодом, но если хотите, я могу предоставить альтернативу.

Это своего рода хакерский способ сделать это, но он работает (проверил его)).Во-первых, вы можете выбросить все эти пользовательские вещи XAML.Затем просто измените ваш Class1 на:

public class Class1
{
    private string _Property1;

    public string Property2 { get; set; }
    public DateTime AddedProperty { get; set; }

    public Class1()
    {

    }

    public Class1(string prop1, string prop2)
    {
        _Property1 = prop1;
        Property2 = prop2;
    }

    public string Property1 
    { 
        get { return _Property1; }
        set { }
    }
}

Хотя доступный набор доступен, он ничего не делает, так что фактически он аналогичен настройке общедоступного / частного сеттера.Правильная документация также поможет, если кому-то еще нужно использовать ваш Class1, и ему интересно, почему «набор» не работает для Property1.

Это может быть план B, если никто не публикует исправление для вашего вышеcode.

Обновление: Если вам также необходимо десериализовать объект, вы можете создать другой объект, который будет выполнять роль посредника для вашего Class1 и процесса сериализации.Вся установка будет выглядеть следующим образом:

public class Class1
{
    public string Property1 { get; private set; }
    public string Property2 { get; set; }
    public DateTime AddedProperty { get; set; }

    public Class1()
    {
    }

    public Class1(string prop1, string prop2) : this()
    {
        Property1 = prop1;
        Property2 = prop2;
    }

    public Class1(Class1DTO dto)
    {
        Property1 = dto.Property1;
    }

    public Class1DTO CreateDTO()
    {
        return new Class1DTO 
        { 
            AddedProperty = AddedProperty,
            Property1 = Property1,
            Property2 = Property2
        };
    }
}

public class Class1DTO
{
    public string Property1 { get; set; }
    public string Property2 { get; set; }
    public DateTime AddedProperty { get; set; }
}

Весь процесс сериализации / десериализации будет выглядеть следующим образом:

var obj = new Class1("value1", "value2");

var dto = obj.CreateDTO();

var objString = Save(dto);

using (var stringReader = new StringReader(objString))
{
    using (var reader = new XamlXmlReader(stringReader))
    {
        var deserializedDTO = XamlServices.Load(reader);
        var originalObj = new Class1(dto);
    }
}

Затем можно изменить модификаторы доступа для точной настройки количества доступадругие люди будут иметь на всей вашей установке (вы можете создать статические методы Serialize / Deserialize для вашего типа Class1 и поместить тип Class1DTO в закрытый вложенный класс, чтобы люди не могли получить к нему доступ и т.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...