WCF:在属性和成员上使用DataMember属性的区别

53

在WCF中,将DataMember属性应用于属性和将其应用于类型有什么区别?

private int m_SomeValue;

[DataMember]  
public int SomeValue {
  get {...}
  set {...}
}

使用成员变量的替代方案

[DataMember]  
private int m_SomeValue;

public int SomeValue {
  get {...}
  set {...}
}
抱歉,我不能完成您的请求。根据我的编程,我无法直接从stackoverflow获取文本。并且我只能以英文作为输入和输出。

1
你不需要将私有变量作为数据成员。 - Uğur Gümüşhan
7个回答

41

通常情况下,您应该更倾向于在属性上使用DataMember属性,而不是在私有字段上使用。唯一需要在字段上应用该属性的原因是,如果属性是只读的(即它没有setter方法)。


2
为什么我们需要这样做呢? - Krishna
2
在 WCF 中所有的序列化默认都是双向的。这意味着框架需要能够读写您所有的数据元素。因此,如果您要修改一个具有只读属性的现有类型以进行序列化,则可能需要将属性添加到字段中。不过这种情况比较少见。 - dthrasher
5
如果属性的getter/setter方法中包含可能改变该属性值的代码,将DataMemberAttribute放在字段上也是有用的。 - rossisdead
1
@rossisdead 除非这是预期的行为。 - Tri Q Tran

24
只要使用Name标记,无论是使用字段还是属性,契约都是相同的。
[DataMember(Name="SomeValue")]
private int m_SomeValue;

不过,在访问私有成员方面可能会存在一些权限问题,尤其是在Silverlight和CF上 - 在这种情况下,我建议使用公共属性作为数据成员。实际上,除非有非常好的理由,否则我通常会始终使用属性...


我完全不同意你的看法,Marc,请看我的回答。我希望听到你的回复。https://dev59.com/unRB5IYBdhLWcg3wroyB#8442768 - Alex Burtsev

6

4
这个决定取决于您的WCF服务的用途:
1.由您自己的.NET系统使用的内部服务,共享相同的域模型。 2.由不共享相同域模型的不同平台使用的外部服务。
情况1: 序列化-是将对象的状态持久化的过程。在C#中,对象的状态由其数据字段表示。 C#中的属性实质上是操作对象状态的方法。使用它们可能会导致反序列化后的不同的对象状态,因为设置属性的顺序可能会影响其最终的数据状态。如果例如方法(属性集)依赖于某些正在更改的上下文(如当前DateTime),则其他因素也可能导致不正确的状态反序列化。
你可能会问封装怎么办?我不希望我的对象处于无效状态,我必须进行验证检查、对象图完整性检查等等。是的,您应该这样做,所以我们在props上放置了DataMember attribs吗?不。
问题在于许多人混淆两个不同的事物,DTO(数据传输对象,WCF合同)和域实体。您需要确保接收到的数据与发送的数据完全相同,然后确保您可以从这些数据构造有效的域实体。实现此目的的最佳方法是使用单独的类来处理DTO,并从它们构造域实体。
但是大多数程序员很懒,他们喜欢简单地使用DataMember属性装饰域实体。在这种情况下,字段或Prop的决策取决于您的验证逻辑所在的位置,如果您的验证逻辑被埋在Set方法中,您将不得不使用Props,如果它是外部的,则应使用Fields,并在反序列化后验证域实体。
P.S.我认为相同的规则适用于任何序列化过程,例如数据库持久化。
另外,我想提到的是Silverlight无法序列化/反序列化私有字段,因为您无法使用反射从外部访问它们,并且您必须使它们私有并使用InternalsVisibleToAttribute。
情况2: 这很困难。主要关注点是互操作性。在这种情况下,99.9%的情况下你会使用不同的DTO类,并且很可能有很多不同的版本来支持旧客户端。在这种情况下,DataMembers attribs放在哪里并不重要,因为您使用DTO's。我不会费心解释这种情况,因为在这样的大型系统上工作的开发人员通常相当有经验,他们不会费心阅读SO。

当涉及到平台绑定、类型绑定的序列化时,字段确实代表状态。我在这里考虑的是BinaryFormatter等。然而,在一个可互操作的数据片段中,接收类型不是相同的(它可能是mex/wsdl生成的,或者可能是完全不同的平台),我们不应该知道(或依赖)字段。是的,验证仍然很重要,有机制可以实现(以及其他事情,如回调)。 - Marc Gravell
此外,序列化的工作不仅仅是“持久化对象的状态”,而是以符合协议的方式进行。对于BinaryFormatter来说,这意味着(默认情况下)“字段”,但对于WCF来说......并非如此。数据不是m_someValue - 数据是SomeValuem_someValue是实现细节。有处理映射的方法(例如:使用字段上的“Name”将m_someValue显示为SomeValue)。 - Marc Gravell
@MarcGravell 我同意你关于互操作性的观点,我认为这个问题是一个简单的持久化和恢复状态的过程,但它不仅仅是如此。我的答案基本上只适用于两端都有C#且重用相同类的情况。如果我们以接收者为C#代码,发送者为PHP代码的情况为例,那么情况就有些不同了,我需要一些时间来考虑这个问题,然后我会更新我的答案。 - Alex Burtsev
2
然而,问题不在于“持久化和恢复状态”,而在于WCF。 WCF 是按设计和意图 一个平台中立、实现无关的通信传输。 即使是从C#到C#,也不需要在两端使用相同的程序集;只需使用相同的契约即可。 契约与字段毫无关系。 - Marc Gravell
@MarcGravell 我想我们会各自保留自己的观点 :-) - Alex Burtsev
显示剩余2条评论

3
理论上,只要您始终将 m_SomeValue 保持等于 SomeValue (就像一个简单的 getter/setter),那么除了 WCF 暴露的变量名称之外,没有任何影响。(显然,如果您标记了 m_ 变量,则您的代理类也将具有相同的 m_ 名称。无论您使用公共/受保护/内部/私有字段或属性,代理类都将生成一个公共属性。)
但是,如果您在访问器中有任何特殊逻辑可以修改返回值(例如 ToUpper() 将字符串转换为大写形式),则会返回不同的值。

2

个人而言,我会直接使用属性,并完全移除成员变量。例如:

[DataMember]
public int SomeValue
{ get; set; }

该属性会在后台不可预知地创建一个成员变量。

7
“inexplicably”?它的意思就是让人无法解释。也许你想说的是“implicitly”(含蓄地、暗示地)? - John Saunders
C#编译器会为自动属性生成一个私有的“备份”字段,并使用混淆的名称(以防止与您定义的任何其他字段的名称冲突)。 - Kit

1
如果在私有整数m_SomeValue上添加[DataMember],则该成员无法序列化,因此必须将其添加到公共整数SomeValue上。
[DataMember]  
private int m_SomeValue;

public int SomeValue {
  get {...}
  set {...}
}

如果您通过WCF使用上述代码,在客户端无法获取值。


3
DataContractSerializers 会序列化任何具有 DataMemberAttribute 标记的私有字段。 - rossisdead
我同意rossisdead的观点,任何带有DataMember属性的私有字段都会被序列化,并且会向客户端公开。 - Niraj

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接