为什么在C#中使用简单属性而不是字段?

43

可能的重复问题:
在C#中,我应该使用公共属性和私有字段还是公共字段进行数据存储?
C# 3.0中自动属性与公共字段的区别

人们似乎教条地坚持使用公共属性而非字段,但为什么在简单属性的情况下如此重要呢?

这个问题在C#中广泛讨论,你可以参考上述链接了解更多信息。

public int Foo { get; set; }

与之如此不同

public int Foo;

就我目前所知,这两者之间有很少的实际区别:

  • 使用反射访问成员(这很少见,而且大多数体面的反射算法将考虑到这一差异)
  • 第二种方式允许您将字段用作 ref 和 out 参数的有效参数,这似乎是使用字段版本的优势
  • 字段在 Remoting 中不起作用(可能是这样,我从未使用过 Remoting,但我想象他们不会?)

除了这些相当罕见的情况外,将 Foo 更改为后来的计算属性不会更改任何代码行。


1
实际上,将变量更改为属性是一种破坏性的更改。 - Shane Fulmer
1
当然会破坏现有的dll,但它不会破坏代码。 - Robert Davis
5
巨坑:https://dev59.com/OHM_5IYBdhLWcg3wrFIt, https://dev59.com/yHM_5IYBdhLWcg3wzmrL,以及与https://dev59.com/pXVC5IYBdhLWcg3wcgmd和https://dev59.com/T3VC5IYBdhLWcg3wtzkQ有关。我们真的需要这样的问题吗? - Aaronaught
@Aaronaught,我同意,我会标记关闭它。 - CrimsonX
使用属性比使用字段更好,因为您可以更改get和set块中的语句,而无需更改依赖于该属性的类。 - SKARVA Bodavula
5个回答

71

使用属性具有几个明显的优点:

  • 如果以后需要额外的逻辑,它可以进行版本控制。将逻辑添加到getter或setter中不会破坏现有的代码。
  • 它可以使数据绑定正常工作(大多数数据绑定框架不支持字段)。

此外,几乎没有缺点。像这样的简单自动属性会被JIT编译器内联,因此没理由不使用。

另外,您提到:

除了这些相当罕见的情况之外,稍后将Foo更改为计算属性不会导致任何代码行更改。

这并不需要更改您的代码,但它确实强制您重新编译所有代码。从字段更改为属性是一种破坏性API更改,这将要求引用您程序集的任何程序集都要重新编译。通过将其设置为自动属性,您只需发布新的二进制文件,并保持API兼容性。这就是上面提到的“版本控制”优点...


1
我倾向于同意,你不想使用属性的唯一原因是要处理 refout 要求,尽管你可以轻松地生成一个临时变量来使用,然后将属性设置为该变量,该变量很快就会被处理掉。 - cossacksman
1
使用属性比使用字段更好,因为您可以更改get和set块中的语句,而无需更改依赖于该属性的类。 - SKARVA Bodavula
如果您使用set块来检查赋值之前的值,那么这将使得对客户端代码的推理更加困难:person.Name = ""; print(person)可能会暗示Name已经被更改,但是setter可能会阻止它。总之,使用“set块”对我来说似乎是一种反功能。 - Micha Wiedenmann
@MichaWiedenmann 这是不良编码实践的问题。如果拒绝了错误值,应该使用异常来拒绝它。 - Miguel Bartelsman

54

一个好的理由是你可以变化get/set的可访问性。

public int Foo {get; protected set;}

2
哇,我实际上没有意识到这一点。+1并感谢。 - Tom 'Blue' Piddock
虽然 readonly 修饰符适用于只应在构造函数中设置的字段,解决了许多需要帮助的情况。而且,现在已经是未来了,您可以使用 public int Foo { get; }(由 readonly 字段支持的自动实现的只读属性)。 - binki
我猜,为了将来的更改,你可以将变量改为属性。但是,有没有定义变量或属性之间差异的良好实践呢? - Mike

31

属性是一种语言元素,它在逻辑上表示类所建模物体的属性。类Car建模了汽车,颜色是汽车的一个属性;因此,Color是Car的属性。

字段是一种语言元素,它代表类的实现细节。你的汽车没有“颜色字段”,因此程序中表示汽车的代码不应该公开暴露名为Color的字段。它可能包含一个私有实现细节,其中属性Color由字段实现,但这是一个私有实现细节,不是模型的公共可访问部分。


1
我更喜欢这个解释,因为它不否认属性实际上是语言中多余的特性,如果你把这个逻辑语义排除在外。唯一剩下的支持者是反射(就像Reed提到的数据绑定)。但是这可以通过对字段进行注释(属性)来实现。 - v.oddou
8
@v.oddou:几乎语言的每个特征都有冗余的部分。例如,方法是不必要的;方法可以只是初始化为lambda的委托类型字段。冗余通常被认为是不好的事情,但实际上并非如此;冗余使事物更容易理解。正如我在答案中提到的那样,拥有两种特征,属性和字段,让我们微妙地向读者传达成员是否在语义上重要或仅仅是类型的机制。 - Eric Lippert
@MichaWiedenmann:许多属性无法被字段替代。 - Eric Lippert
@LucaCremonesi:在那个反事实的世界里,有很多事情我会去修复,但这并不是其中之一。有些语言特性真的是极易误导;代码看起来应该可以工作,但实际上却不能。还有一些语言特性是令人困惑冗余的。语言中的安全特性应该被设计成难以编写错误程序;通过将违反次要最佳实践规则的行为定为非法行为来防止人们违规,通常不是最好的时间和精力利用方式。 - Eric Lippert
1
@Karol:嗯,有利有弊。例如:C#必须能够良好地与其他.NET语言进行交互,并且对于Java和C++的用户来说必须是熟悉的。而且在某些情况下,出于性能原因,您希望直接访问结构体的字段,特别是用于与C++进行交互的纯旧数据结构。并且您无法通过引用传递属性。因此,并不是没有用例可用。但请注意,我提到的所有这些用例都意味着字段是机制。 - Eric Lippert
显示剩余5条评论

8

主要是出于惯例。

唯一的确凿理由是,如果您稍后需要从字段更改为属性,则引用您的所有程序集都需要重新编译。

反射有时会涉及其中,但非常少见。某些序列化类型基于属性。


7

你可以将属性标记为虚拟的,并在派生类中重写它们的实现。这对于许多库(例如NHibernate用于实现延迟加载的代理类)中包装对象的操作非常重要。但是,这在字段上不可行。


1
这种说法完全否认了字段可以被封装在访问器和修改器中并获得相同的功能。这尤其正确,因为属性通常就是这样实现的,它们封装了一个字段(只要它们不是自动属性)。于是,回到原点,属性就没用了。 - v.oddou
@v.oddou 甚至在技术上,自动属性也包装字段 ——只是编译器生成的语法糖! - binki

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