.net 抽象类的序列化

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/1327064/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-09-03 13:12:00  来源:igfitidea点击:

Serialization of an abstract class

.netserializationabstract

提问by Natrium

I'm trying to serialize, and I am facing a problem with an abstact class.

我正在尝试序列化,但我正面临 abstact 类的问题。

I googled for an answer, and I found this blogitem. I tried that and that work.

我在谷歌上搜索了一个答案,我找到了这个 blogitem。我试过那个和那个工作。

Ok, very nice. But check out the comment on the item:

好的,很不错。但请查看对该项目的评论:

This methodology seems to be hiding the true problem and that is an inaccurate implementation of OO design patterns, namely the factory pattern.

Having to change the base class to reference any new factory class is self-defeating.

With a little after-thought, the code can be changed to where any derived type can be associated with the abstract class (through the miracle of interfaces) and no XmlInclude would be required.

I suggest further research into factory patterns which seems to be what you are trying to implement here.

这种方法似乎隐藏了真正的问题,即 OO 设计模式(即工厂模式)的不准确实现。

必须更改基类以引用任何新的工厂类是弄巧成拙。

稍加考虑,代码可以更改为任何派生类型都可以与抽象类相关联(通过接口的奇迹)并且不需要 XmlInclude。

我建议进一步研究工厂模式,这似乎是您在这里尝试实施的。

What is commenter talking about? He is kinda vague. Can someone explain it more in detail (with an example)? Or is he just talking nonsense?

评论者在说什么?他有点含糊。有人可以更详细地解释一下吗(举个例子)?还是他只是在胡说八道?

Update (after reading the first answer)

更新(阅读第一个答案后)

Why does the commentor talk about

为什么评论者会谈论

factory pattern

工厂模式

and

the code can be changed to where any derived type can be associated with the abstract class (through the miracle of interfaces)

代码可以更改为任何派生类型都可以与抽象类相关联的地方(通过接口的奇迹)

?

?

Does he want to make an interface, like this?

他要不要做一个这样的界面?

public interface IWorkaround
{
    void Method();
}

public class SomeBase : IWorkaround
{
    public void Method()
    {
        // some logic here
    }
}

public class SomeConcrete : SomeBase, IWorkaround
{
    public new void Method()
    {
        base.Method();
    }
}

回答by Marc Gravell

He is both right and wrong at the same time.

他既对又错。

With things like BinaryFormatter, this is a non-issue; the serialized stream contains full type metadata, so if you have:

对于像BinaryFormatter这样的东西,这不是问题;序列化流包含完整的类型元数据,因此如果您有:

[Serializable] abstract class SomeBase {}
[Serializable] class SomeConcrete : SomeBase {}
...
SomeBase obj = new SomeConcrete();

and serialize obj, then it includes "I'm a SomeConcrete" in the stream. This makes life simple, but is verbose, especially when repeated. It is also brittle, as it demands the same implementation when deserializing; bad for either different client/server implementations, or for long-term storage.

和序列化obj,然后它SomeConcrete在流中包含“我是一个”。这使生活变得简单,但很冗长,尤其是在重复时。它也很脆弱,因为它在反序列化时需要相同的实现;对不同的客户端/服务器实现或长期存储都不利。

With XmlSerializer(which I guess the blog is talking about), there is no metadata - but the element names (or the xsi:typeattributes) are used to help identify which is used. For this to work, the serializer needs to know in advancewhat names map to which types.

使用XmlSerializer(我猜博客正在谈论),没有元数据 - 但元素名称(或xsi:type属性)用于帮助识别使用的元素。为此,序列化程序需要提前知道哪些名称映射到哪些类型。

The simplest way to do this is to decorate the base-class with the subclasses we know about. The serializer can then inspect each of these (and any additional xml-specific attributes) to figure out that when it sees a <someConcreteType>element, that maps to a SomeConcreteinstance (note that the names don't need to match, so it can't just look for it by name).

最简单的方法是用我们知道的子类装饰基类。然后序列化程序可以检查其中的每一个(以及任何其他特定于 xml 的属性),以确定当它看到一个<someConcreteType>元素时,该元素映射到一个SomeConcrete实例(请注意,名称不需要匹配,因此它不能只是按名称查找)。

[XmlInclude(typeof(SomeConcrete))]
public abstract class SomeBase {}
public class SomeConcrete : SomeBase {}
...
SomeBase obj = new SomeConcrete();
XmlSerializer ser = new XmlSerializer(typeof(SomeBase));
ser.Serialize(Console.Out, obj);

However, if he is a purist (or the data isn't available), then there is an alternative; you can specify all this data separatelyvia the overloaded constructor to XmlSerializer. For example, you might lookup the set of known subtypes from configuration (or maybe an IoC container), and setup the constructor manually. This isn't very tricky, but it is tricky enough that it isn't worth it unless you actually need it.

然而,如果他是一个纯粹主义者(或者数据不可用),那么还有另一种选择;您可以通过重载的构造函数分别指定所有这些数据XmlSerializer。例如,您可以从配置(或者可能是 IoC 容器)中查找一组已知子类型,并手动设置构造函数。这不是很棘手,但它足够棘手,除非您确实需要它,否则它不值得。

public abstract class SomeBase { } // no [XmlInclude]
public class SomeConcrete : SomeBase { }
...
SomeBase obj = new SomeConcrete();
Type[] extras = {typeof(SomeConcrete)}; // from config
XmlSerializer ser = new XmlSerializer(typeof(SomeBase), extras);
ser.Serialize(Console.Out, obj);

Additionally, with XmlSerializerif you go the custom ctor route, it is important to cache and re-use the XmlSerializerinstance; otherwise a new dynamic assembly is loaded per usage - very expensive (they can't be unloaded). If you use the simple constructor it caches and re-uses the model, so only a single model is used.

此外,XmlSerializer如果您使用自定义 ctor 路由,缓存和重用XmlSerializer实例很重要;否则每次使用都会加载一个新的动态程序集 - 非常昂贵(它们无法卸载)。如果您使用简单的构造函数,它会缓存并重用模型,因此只使用单个模型。

YAGNI dictates that we should choose the simplest option; using [XmlInclude]removes the need for a complex constructor, and removes the need to worry about caching the serializer. The other option is there and is fully supported, though.

YAGNI 规定我们应该选择最简单的选项;using[XmlInclude]不需要复杂的构造函数,并且不需要担心缓存序列化程序。不过,另一个选项是存在的并且完全受支持。



Re your follow-up questions:

回复您的后续问题:

By "factory pattern", he's talking about the case where your code doesn't know aboutSomeConcrete, perhaps due to IoC/DI or similar frameworks; so you might have:

通过“工厂模式”,他谈论的是您的代码不知道的情况SomeConcrete,可能是由于 IoC/DI 或类似框架;所以你可能有:

SomeBase obj = MyFactory.Create(typeof(SomeBase), someArgsMaybe);

Which figures out the appropriate SomeBaseconcrete implementation, instantiates it and hands it back. Obviously, if our code doesn't know about the concrete types (because they are only specified in a config file), then we can't use XmlInclude; but we canparse the config data and use the ctor approach (as above). In reality, most times XmlSerializeris used with POCO/DTO entities, so this is an artificial concern.

它找出适当的SomeBase具体实现,将其实例化并将其交还。显然,如果我们的代码不知道具体类型(因为它们只在配置文件中指定),那么我们就不能使用XmlInclude; 但是我们可以解析配置数据并使用 ctor 方法(如上所述)。实际上,大多数情况下XmlSerializer都是与 POCO/DTO 实体一起使用的,因此这是一个人为的关注点。

And re interfaces; same thing, but more flexible (an interface doesn't demand a type hierarchy). But XmlSerializerdoesn't support this model. Frankly, tough; that isn't its job. Its job is to allow you to store and transport data. Not implementation. Any xml-schema generated entities won't havemethods. Data is concrete, not abstract. As long as you think "DTO", the interface debate is a non-issue. People who are vexed by not being able to use interfaces on their boundary haven't embraced separation of concerns, i.e. they are trying to do:

并重新接口;同样的事情,但更灵活(接口不需要类型层次结构)。但是XmlSerializer不支持这种模式。坦率地说,强硬;那不是它的工作。它的工作是允许您存储和传输数据。不是执行。任何支持XML架构生成的实体不会方法。数据是具体的,而不是抽象的。只要你认为“DTO”,接口争论就不是问题。因无法在其边界上使用接口而烦恼的人还没有接受关注点分离,即他们正在尝试这样做:

Client runtime entities <---transport---> Server runtime entities

rather than the less restrictive

而不是限制较少

Client runtime entities <---> Client DTO <--- transport--->
           Server DTO <---> Server runtime entities

Now, in many (most?) cases the DTO and entities canbe the same; but if you are trying to do something that the transport doesn't like, introduce a DTO; don't fight the serializer. The same logic applies when people are struggling to write their object:

现在,在许多(大多数?)情况下,DTO 和实体可以相同;但是如果你想做一些运输不喜欢的事情,引入一个 DTO;不要与序列化程序作斗争。当人们努力编写他们的对象时,同样的逻辑适用:

class Person {
    public string AddressLine1 {get;set;}
    public string AddressLine2 {get;set;}
}

as xml of the form:

作为表单的xml:

<person>
    <address line1="..." line2="..."/>
</person>

If you want this, intoduce a DTO that corresponds to the transport, and map between your entity and the DTO:

如果你想要这个,引入一个对应于传输的 DTO,并在你的实体和 DTO 之间进行映射:

// (in a different namespace for the DTO stuff)
[XmlType("person"), XmlRoot("person")]
public class Person {
    [XmlElement("address")]
    public Address Address {get;set;}
}
public class Address {
    [XmlAttribute("line1")] public string Line1 {get;set;}
    [XmlAttribute("line2")] public string Line2 {get;set;}
}

This also applies to all those other niggles like:

这也适用于所有其他琐事,例如:

  • why do I need a parameterless constructor?
  • why do I need a setter for my collection properties?
  • why can't I use an immutable type?
  • why must my type be public?
  • how do I handle complex versioning?
  • how do I handle different clients with different data layouts?
  • why can't I use interfaces?
  • etc, etc
  • 为什么我需要一个无参数的构造函数?
  • 为什么我的集合属性需要一个 setter?
  • 为什么我不能使用不可变类型?
  • 为什么我的类型必须是公开的?
  • 我如何处理复杂的版本控制?
  • 如何处理具有不同数据布局的不同客户端?
  • 为什么我不能使用接口?
  • 等等等等

You don't always have these problems; but if you do - introduce a DTO (or several) and your problems go away. Taking this back to the question about interfaces; the DTO types might not be interface-based, but your runtime/business types can be.

你并不总是有这些问题;但是如果你这样做 - 引入一个(或几个)DTO,你的问题就会消失。回到关于接口的问题;DTO 类型可能不是基于接口的,但您的运行时/业务类型可以。

回答by Joao Pedro Leite S Lisboa

**Example of Enum Abstract Serializer

Simple example of an abstract enum ...(Java)(Spring-Boot) 
----------------------------------------------------------------------------------**


@JsonSerialize(using = CatAbstractSerializer.class)
public enum CatTest implements Tes{

    TYPE1(1, "Type 1"), TYPE2(2, "Type 2");

    private int id;
    private String nome;

    private CatTest(int id, String nome) {
        // TODO Auto-generated constructor stub

        this.id = id;
        this.nome = nome;


    }
    @JsonValue
    public int getId() {
        return id;
    }
    @JsonSetter
    public void setId(int id) {
        this.id = id;
    }
    @JsonValue
    public String getNome() {
        return nome;
    }
    @JsonSetter
    public void setNome(String nome) {
        this.nome = nome;
    }
    @Override
     @JsonValue
     public String toString() {
            return nome;
     }
     @JsonCreator
        public static CatTest fromValueString(String nome) {
            if(nome == null) {
                throw new IllegalArgumentException();
            }
            for(CatTest nomeSalvo : values()) {
                if(nome.equals(nomeSalvo.getNome())) {
                    return nomeSalvo;
                }
            }
            throw new IllegalArgumentException();
        }


}


public interface Tes {

    @JsonValue
    int getId();

    @JsonValue
    String getNome();

    @JsonSetter
    void setId(int id);

    @JsonSetter
    void setNome(String nome);


}

public class CatAbstractSerializer<T extends Tes> extends JsonSerializer<T> {

    @Override
    public void serialize(T value, JsonGenerator gen, SerializerProvider serializers)
            throws IOException, JsonProcessingException {
        // TODO Auto-generated method stub

        gen.writeStartObject();
        gen.writeFieldName("id");
        gen.writeNumber(value.getId());
        gen.writeFieldName("name");
        gen.writeString(value.getNome());
        gen.writeEndObject();

    }

}