公共get / private set属性的XAML序列化

时间:2011-11-29 10:58:28

标签: c# xaml c#-4.0

我正在使用System.Xaml.XamlServices.Save方法序列化具有公共getter / private setter属性的对象,并且通过设计忽略这些属性。我试图实现advice如何覆盖默认的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 个答案:

答案 0 :(得分:1)

这里的问题是您正在尝试编写只读和私有属性。

根据XAML标准,语法上唯一的readonly属性是List,Dictionary和static成员:

  

3.3.1.6。只有列表,词典或静态成员可能是只读的   如果[值类型] [是列表]也不是[值类型] [是字典],[静态]也不是真,[只读]必须为假。

查看有关MSDN语法详细信息的here

标准本身可以下载here

您还会注意到,这里只有公共属性有任何相关性(来自上面链接的msdn):

  

要通过属性语法进行设置,属性必须是公共的,并且必须是可写的。支持类型系统中的属性值必须是值类型,或者必须是在访问相关支持类型时可由XAML处理器实例化或引用的引用类型。

     

对于WPF XAML事件,引用为属性的事件   名称必须是公开的并且有公共代表。

     

属性或事件必须是该类或结构的成员   由包含的对象元素实例化。

如果你考虑一下,你就会明白为什么。

整个C#标准实际上是围绕使用通过使用公共属性和方法进行交互的类构建的。通过这样做,其他类不需要知道除了它们之外的类中存在什么。每个类都可以被视为一个黑盒子,其中公共属性和方法是类与其他代码的接口。

Here's有关XAML序列化的信息博客。

我个人会问自己为什么需要序列化/反序列化私有成员属性。

答案 1 :(得分:1)

事实证明我的初始代码的几个调整解决了这个问题。这是最终的解决方案:

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;
    }
}

此自定义允许使用公共getter和public / internal / protected / private setter序列化/反序列化属性。它忽略了所有其他属性。它还序列化内部类的实例。

答案 2 :(得分:0)

我不太确定上面的代码有什么问题,但如果你愿意,我可以提供替代方案。

这是一种hacky方式,但它可以工作(测试它)。首先,您可以丢弃所有自定义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 { }
    }
}

虽然可以访问set访问器,但它不会执行任何操作,因此实际上这与公共getter / private setter设置相同。如果其他人需要使用您的Class1,那么正确的文档也会有所帮助,并且想知道为什么'set'不适用于Property1。

这可能是一个计划B,以防没有人为您的上述代码发布修复程序。

更新:如果您还需要反序列化对象,则可以创建另一个对象作为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);
    }
}

然后,您可以更改访问修饰符以微调其他人在整个设置中的访问量(您可以在Class1类型上创建静态Serialize / Deserialize方法并将Class1DTO类型推送到私有嵌套类中,以便人们可以不能访问它等。)。