Nat*_*ium 13 .net serialization abstract
我正在尝试序列化,我面临着一个抽象类的问题.
我搜索了一个答案,我发现了这个博客.我试过那个和那个工作.
好的,非常好.但请查看该项目的评论:
这种方法似乎隐藏了真正的问题,这是OO设计模式的不准确实现,即工厂模式.
必须更改基类以引用任何新工厂类是弄巧成拙的.
经过一番思考,代码可以更改为任何派生类型可以与抽象类相关联(通过接口的奇迹),并且不需要XmlInclude.
我建议进一步研究工厂模式,这似乎是你试图在这里实现的.
什么是评论者谈论?他有点模糊.有人可以更详细地解释它(举个例子)吗?或者他只是胡说八道?
更新(阅读第一个答案后)
为什么评论员会谈论
工厂模式
和
代码可以更改为任何派生类型可以与抽象类相关联(通过接口的奇迹)
?
他想制作这样的界面吗?
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();
}
}
Run Code Online (Sandbox Code Playgroud)
Mar*_*ell 41
他同时也是对的.
有了这样的话BinaryFormatter,这不是问题; 序列化流包含完整类型元数据,因此如果您有:
[Serializable] abstract class SomeBase {}
[Serializable] class SomeConcrete : SomeBase {}
...
SomeBase obj = new SomeConcrete();
Run Code Online (Sandbox Code Playgroud)
和序列化obj,然后它包括SomeConcrete流中的"我是一个".这使得生活变得简单,但是很冗长,特别是在重复时.它也很脆弱,因为它在反序列化时需要相同的实现; 对于不同的客户端/服务器实现或长期存储都不利.
有XmlSerializer(我猜博客正在讨论),没有元数据 - 但元素名称(或xsi:type属性)用于帮助识别使用的是哪些.为此,序列化程序需要事先知道哪些名称映射到哪些类型.
最简单的方法是使用我们知道的子类来装饰基类.然后,序列化程序可以检查这些中的每一个(以及任何其他特定于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);
Run Code Online (Sandbox Code Playgroud)
但是,如果他是一个纯粹主义者(或数据不可用),那么还有另一种选择; 您可以通过重载的构造函数分别指定所有这些数据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);
Run Code Online (Sandbox Code Playgroud)
此外,XmlSerializer如果你去自定义ctor路由,重要的是缓存和重用XmlSerializer实例; 否则每次使用都会加载一个新的动态组件 - 非常昂贵(它们无法卸载).如果使用简单构造函数,它会缓存并重新使用模型,因此只使用单个模型.
YAGNI要求我们选择最简单的选择; using [XmlInclude]删除了对复杂构造函数的需求,并且无需担心缓存序列化程序.另一种选择是在那里,并得到完全支持.
重新提出你的后续问题:
通过"工厂模式",他谈论的是你的代码不知道 的情况SomeConcrete,可能是由于IoC/DI或类似的框架; 所以你可能有:
SomeBase obj = MyFactory.Create(typeof(SomeBase), someArgsMaybe);
Run Code Online (Sandbox Code Playgroud)
其中列出了适当的SomeBase具体实现,实例化并将其交还.显然,如果我们的代码不知道具体的类型(因为它们只在配置文件中指定),那么我们就不能使用XmlInclude; 但是我们可以解析配置数据并使用ctor方法(如上所述).实际上,大多数时候XmlSerializer都与POCO/DTO实体一起使用,因此这是一个人为关注的问题.
并重新接口; 同样的事情,但更灵活(接口不要求类型层次结构).但XmlSerializer不支持这种模式.坦率地说,很难; 那不是它的工作.它的工作是允许您存储和传输数据.没有实施.任何支持XML架构生成的实体不会有方法.数据是具体的,而不是抽象的.只要你想到"DTO",界面争论就不是问题了.因无法在边界上使用界面而烦恼的人并未接受关注点的分离,即他们试图这样做:
Client runtime entities <---transport---> Server runtime entities
Run Code Online (Sandbox Code Playgroud)
而不是限制较少的
Client runtime entities <---> Client DTO <--- transport--->
Server DTO <---> Server runtime entities
Run Code Online (Sandbox Code Playgroud)
现在,在许多(大多数?)案例中,DTO和实体可以是相同的; 但是如果你想做一些运输不喜欢的东西,那就介绍一下DTO; 不要打串行器.当人们努力写下他们的对象时,同样的逻辑也适用:
class Person {
public string AddressLine1 {get;set;}
public string AddressLine2 {get;set;}
}
Run Code Online (Sandbox Code Playgroud)
作为形式的xml:
<person>
<address line1="..." line2="..."/>
</person>
Run Code Online (Sandbox Code Playgroud)
如果需要,请插入与传输相对应的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;}
}
Run Code Online (Sandbox Code Playgroud)
这也适用于所有其他琐事,如:
你并不总是有这些问题; 但如果你这样做 - 介绍一个DTO(或几个),你的问题就会消失.回过头来讨论接口问题; DTO类型可能不是基于接口的,但您的运行时/业务类型可以是.