【问题标题】:How to dynamically choose what inherited properties will be serialized?如何动态选择要序列化的继承属性?
【发布时间】:2014-03-20 14:09:20
【问题描述】:

我有一个带有几个属性的基类和三个派生类。

我想序列化一个包含所有三个派生类的对象,但每个派生类都应该从基类公开一组不同的属性。

我想用 XmlAttributeOverrides 动态地做到这一点,并尝试了几种不同的方法来做到这一点,但没有真正做到这一点。

[Serializable]
public class A
{
    public string Property1 { get; set; }
    public string Property2 { get; set; }
}

[Serializable]
public class B : A
{
}

[Serializable]
public class C : A
{
}

[Serializable]
public class Container
{
    public B B { get; set; }
    public C C { get; set; }
}


class Program
{
    static void Main(string[] args)
    {
        MemoryStream memoryStream = new MemoryStream();
        StreamWriter encodingWriter = new StreamWriter(memoryStream, Encoding.Unicode);

        var xmlWriter = XmlWriter.Create(encodingWriter, new XmlWriterSettings
        {
            Indent = false,
            OmitXmlDeclaration = true,
        });
        XmlAttributeOverrides overrides = new XmlAttributeOverrides();
        XmlAttributes attribute = new XmlAttributes();
        attribute.XmlIgnore = true;
        overrides.Add(typeof(B), "Property1", attribute);
        overrides.Add(typeof(C), "Property2", attribute);
        var container = new Container
            {
                B = new B {Property1 = "B property 1", Property2 = "B property 2"},
                C = new C {Property1 = "C property 1", Property2 = "C property 2"}
            };

        var xmlSerializer = new XmlSerializer(typeof(Container), overrides);
        xmlSerializer.Serialize(xmlWriter, container);

        var result = Encoding.Unicode.GetString(memoryStream.ToArray());
    }
}

在上面的代码中,结果字符串将包含 A 在 B 和 C 中的所有属性,但我真的希望它只包含 B Property2 和 C Property1(因为我已经为它们设置了 XmlIgnore 属性)。

我该怎么做?

编辑:预期的 XML:

<Container xmlns:xsd="http://www.w3.org/2001/XMLSchema"    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"><B><Property2>B property 2</Property2></B><C><Property1>C property 1</Property1></C></Container>

实际的 XML:

<Container xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"><B><Property1>B property 1</Property1><Property2>B property 2</Property2></B><C><Property1>C property 1</Property1><Property2>C property 2</Property2></C></Container>

编辑 2:以上是可视化问题的示例,但我将详细说明为什么我们需要这样做。

我们有一个容器类(如上),其中包含不同类型的派生对象(如上)。 当我们将 Container-class 中的数据暴露给其他人时,我们希望能够仅暴露某些可在其他地方配置的字段(可能是敏感数据或其他)。

我们在 XmlAttributeOverrides 的帮助下为暴露的属性设置 XmlIgnore 属性。这适用于大多数类型的对象(没有继承),但现在我们需要以不同的方式(可配置)序列化不同的派生对象。

所以在上面的例子中,一些客户决定从 B 类中排除 Property1,从 C 类中排除 Property2,因此我希望 XML 看起来像上面那样。 但是,这不适用于上面的代码;似乎 XmlSerializer 使用基类 A 中的属性设置,而不是使用各自派生类 B 和 C 中的设置。

【问题讨论】:

  • 虽然与您的问题无关,但请注意,使用XmlSerializer 时不需要[Serializable] 属性。
  • 我最终(由于其他原因)单独序列化所有部分(通过反射),这在解决另一个问题的同时也解决了这个问题。 @ChrisSinclair 虽然有足够的解决方案,所以我将其标记为答案。

标签: c# serialization


【解决方案1】:

很难从您的问题中确切地看出您正在寻找什么 XML 输出,因此我将仅举一个示例,您可以根据需要对其进行修改。 (编辑:似乎我走运了;下面的示例实现与您编辑的所需 XML 结果相匹配)

您可以使用鲜为人知的ShouldSerializePROPERTYNAME 方法动态指示XmlSerializer 忽略属性。例如:

public class A
{
    public string Property1 { get; set; }
    public string Property2 { get; set; }

    public virtual bool ShouldSerializeProperty1()
    {
        return true;
    }

    public virtual bool ShouldSerializeProperty2()
    {
        return true;
    }
}

这些方法可以被子类覆盖以忽略这些属性:

public class B : A
{
    public override bool ShouldSerializeProperty1()
    {
        return false;
    }
}

public class C : A
{
    public override bool ShouldSerializeProperty2()
    {
        return false;
    }
}

同样,您可以通过Container分配的其他属性来控制方法的返回值:

public class A
{
    public string Property1 { get; set; }
    public string Property2 { get; set; }

    [XmlIgnore]
    internal bool _ShouldSerializeProperty1 = true;
    [XmlIgnore]
    internal bool _ShouldSerializeProperty2 = true;

    public bool ShouldSerializeProperty1()
    {
        return _ShouldSerializeProperty1;
    }

    public bool ShouldSerializeProperty2()
    {
        return _ShouldSerializeProperty2;
    }
}

然后在将BC 分配给Container 时,您可以设置这些标志:

public class Container
{
    private B _B;
    public B B 
    { 
        get
        {
            return _B;
        }
        set
        {
            if (value != null)
            {
                value._ShouldSerializeProperty1 = false;
                value._ShouldSerializeProperty2 = true;
            }
            _B = value;
        }
    }

    private C _C;
    public C C
    {
        get
        {
            return _C;
        }
        set
        {
            if (value != null)
            {
                value._ShouldSerializeProperty1 = true;
                value._ShouldSerializeProperty2 = false;
            }
            _C = value;
        }
    }
}

这些只是一些示例(我并没有声称在这里使用了最佳实践)来演示如何使用ShouldSerialize。您可能希望尽可能适应您的特定用途。

编辑:鉴于您更新的帖子,还有另一种可能性,但需要一些额外的工作来定义您的子类和一些 DRY 违规(尽管对于序列化,有时这没关系)。

首先将A的属性定义为virtual,并在子类中将它们覆盖为基本包装器:

public class A
{
    public virtual string Property1 { get; set; }
    public virtual string Property2 { get; set; }
}

public class B : A
{
    public override string Property1 { get { return base.Property1; } set { base.Property1 = value; } }
    public override string Property2 { get { return base.Property2; } set { base.Property2 = value; } }
}

public class C : A
{
    public override string Property1 { get { return base.Property1; } set { base.Property1 = value; } }
    public override string Property2 { get { return base.Property2; } set { base.Property2 = value; } }
}

那么既然(我假设)您正在通过这些配置管理/构建您的 XmlSerializer 序列化,请为 所有 基类的属性包含 XmlIgnore 覆盖 em>:

overrides.Add(typeof(A), "Property1", attribute);
overrides.Add(typeof(A), "Property2", attribute);

然后包含XmlIgnore覆盖您希望真正忽略的子类属性:

overrides.Add(typeof(B), "Property2", attribute);
overrides.Add(typeof(C), "Property1", attribute);

这将产生您想要的输出。

【讨论】:

  • 我已经用预期的 XML 编辑了问题。我的感觉是,应该有一个比使用许多特定的 ShouldSerialize 属性进行装饰更简单的解决方案。提供的代码是一个解决问题的示例,我的实际代码使用了更多带有反射和东西的通用代码来确定哪些属性应该被序列化而不是。
  • @Joel:嗯,您所做的通常超出了 XML 序列化的常见用例场景,因此“不容易”也就不足为奇了。 理想情况下,您希望您的数据模型得到修复,并使您的 C# 类与您的 XML 架构一对一匹配。您的应用程序代码应该从您的 XML 序列化代码中抽象出来,这样您就不必像这样执行愚蠢的操作。 (是的,这可能意味着您可能有一个特定的 Container_BContainer_C 类,这似乎不是从一个常见的 A 类派生的。然后在使用您的应用程序逻辑时转换为/从这些类。)
  • 只是为了扩展,如果您使用反射和“东西”来确定应该序列化哪些属性,可能很难评论您的问题的最佳解决方案。我能给出的最好建议(甚至可能不适用)是尽可能从序列化问题中抽象出你的应用程序逻辑。保持数据序列化层简单,否则在尝试更改/更新架构时只会导致头痛(就像现在一样)或以后。
  • 我在原帖中添加了额外的信息,希望能更好地描述需求,如果需要我会继续提供更多信息。 :)
  • @Joel:我得到了一张更好的照片。不幸的是,几年前我还尝试过使用继承来控制属性与 XML 之间的格式。老实说,我不记得具体细节(如果它们使用覆盖或隐藏,或者如果您基于基类或子类使用XmlSerializers 等)但我得出的结论是避免它。但是,我破解了一种方法,因此您可以将其关闭,请参阅我对答案的编辑。
猜你喜欢
  • 2017-11-04
  • 2013-07-10
  • 1970-01-01
  • 1970-01-01
  • 2015-09-14
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多