公共字段与自动属性之间的区别

406

我们经常听说保护封装性应该通过为类字段创建getter和setter方法(在C#中为属性),而不是将字段暴露给外部世界。

但是,有很多时候,一个字段只是用来保存值,不需要任何计算就能设置或获取。对于这些情况,我们只需要进行以下操作:

public class Book
{
    private string _title;

    public string Title
    {
          get => _title; 
          set => _title = value;
    }
}

其实,我有一个坦白的话要说,我无法忍受写这么多代码(真的,不是因为编写代码本身,而是因为看着它),所以我采用了公共字段。

后来C# 3.0出现了,我发现它们添加了自动属性:

public class Book
{
    public string Title { get; set; } 
}

哪个更整洁,我很感激它,但实际上,与公共字段相比有什么不同吗?

public class Book
{
    public string Title;
}

可能是在C# .NET 3.5+中属性和字段的区别的重复问题。 - nawfal
1
我倾向于将任何非私有的内容都作为属性处理,因为发现在以后的过程中必须重构一个字段成为属性会带来一些不必要的麻烦。属性,字段和方法。哦,天呐! 指出了过去曾经困扰我的一个不兼容性问题。 - Steven Wexler
4
prop 代码片段能够快速创建属性。只需要输入 prop 然后按 Tab 键即可。 - Tono Nam
14个回答

197

在我以前提出的一个相关问题中,有一个链接指向Jeff的博客文章,解释了一些差异。

属性 vs. 公共变量

  • Reflection works differently on variables vs. properties, so if you rely on reflection, it's easier to use all properties.
  • You can't databind against a variable.
  • Changing a variable to a property is a breaking change. For example:

    TryGetTitle(out book.Title); // requires a variable
    

35
将变量改为属性是一项破坏性的更改。当然,这仅适用于编写可重用库的情况,而大多数开发人员并没有这样做。 - Steven
37
属性,即使是自动属性,也可以是虚拟的,而字段则不行。因此,基类可以有一个简单的后备字段实现,就像编译器为自动属性生成的那样,而派生类可以执行额外的验证或其他逻辑/计算。 - KeithS
35
一个字段是一个“变量”,可以通过引用(refout关键字)进行传递,而属性是一对访问器,不能通过引用传递。例如,使用 outbool success = TryGetMyTitle(out myBook.Title); 将适用于字段而不适用于属性。这是为什么从字段更改为属性是一种破坏性更改的明显例子! - Jeppe Stig Nielsen
3
不,这没什么意义,因为属性是一对访问器方法,不是变量。通常做法是声明一个本地变量(可能会读取属性并将其值放入本地变量中),将本地变量作为 ref/out 传递,然后将属性设置为本地变量所具有的值。但是调用的方法本身并没有访问该属性,而是访问你在那里创建的本地变量。 - Jeppe Stig Nielsen
5
没错,虽然在 C# 6 中,你可以使用 public int Foo { get; } 来创建带有只读后备字段的自动属性。 - Michael Stum
显示剩余6条评论

94

除了API问题之外,我发现使用属性最有价值的是调试。

CLR调试器不支持数据断点(大多数本地调试器都支持)。因此,无法在类的特定字段读取或写入时设置断点。在某些调试场景中,这非常受限制。

由于属性被实现为非常简单的方法,因此可以在读取和写入它们的值时设置断点。这使它们比字段更具优势。


13
十年后,数据断点出现了,至少对于.NET Core :) - Luaan

81

将字段更改为属性会破坏合同(例如,需要重新编译所有引用代码)。因此,当您与其他类有交互点时 - 任何公共(通常是受保护的)成员,您都要为未来的增长做好规划。通过始终使用属性来实现。

今天将其更改为自动属性很容易,而三个月后你可能意识到想要将其设置为延迟加载,并在 getter 中放置一个空值检查。如果您使用了字段,则最多需要重新编译更改,最坏的情况下则不可能,这取决于谁和什么依赖于您的程序集。


69

只是因为没有人提到:你无法在接口中定义字段。因此,如果您必须实现一个定义了属性的特定接口,自动属性有时是一个非常好的功能。


58

一个经常被忽视且在其他答案中没有提到的重大区别是覆盖(overriding)。你可以声明属性为虚拟并覆盖它们,但对于公共成员字段,你不能这样做。


为什么不直接说:封装。 - VimNing
将公共字段更改为属性并不是封装。 - Danon
@Danon 将其更改为属性将使您能够在属性中添加逻辑,这样它更清晰地成为封装。 - Zaid Masud
@ZaidMasud 不是很准确,它仍然与初始公共字段值和类型耦合。它不能保护您免受数据结构更改的影响,因此无法实现封装。 - Danon
重点是它可能是耦合的,但不需要这样,并且实现可以随后更改以解耦,而无需更改依赖类。对于公共成员域则不能这样说。 - Zaid Masud

13

版本控制和API稳定性非常重要。在第一个版本中并没有什么区别,但是如果你在第二个版本中决定把这个东西变成一个具有某种错误检查类型的属性,你不需要更改你的API-除了属性定义之外,任何地方都不需要进行代码更改。


为什么不直接说:封装。 - VimNing
2
@Rainning 这样更清晰明了。 - FailedUnitTest

11

使用自动实现属性而不是公共字段的另一个优点是,您可以将set访问器设置为私有或受保护,从而为定义它的对象类提供比公共字段更好的控制。


10

将一个变量声明为 public 并没有问题。但是要记住,用 private 声明变量并创建 getter/setter 并不等同于封装。在我看来,如果您不关心属性的其他特性,则可以考虑将其设置为 public


9
这些微不足道的属性让我感到悲伤。它们是最糟糕的模拟行为,而且 C# 中对公共字段的厌恶需要停止。公共字段最大的争议在于未来的可扩展性:如果您之后决定需要为 getter 和 setter 添加额外逻辑,则必须在使用该字段的任何其他代码中进行大规模重构。这在其他语言如 C++ 和 Java 中肯定是正确的,因为调用 getter 和 setter 方法的语义与设置和获取字段的语义非常不同。然而,在 C# 中,访问属性的语义与访问字段的语义完全相同,因此您的 99% 代码应该完全不受此影响。
我见过的唯一一个将字段更改为属性实际上是源代码级别的破坏性变化的例子是:
    TryGetTitle(out book.Title); // requires a variable

我必须问一下,为什么你要将其他类的字段作为引用传递?假设这不是一个属性似乎是真正的编码错误。假设你可以直接写入另一个类中的数据,而你对该类一无所知,这是一种不好的实践。创建自己的本地变量并从中设置book.Title。任何像这样做的代码都应该被打破。
我看到的其他反对意见:
将字段更改为属性会破坏二进制兼容性,并需要重新编译使用它的任何代码:如果您正在编写用作闭源库分发的代码,则这是一个问题。在这种情况下,请确保您的用户界面类没有公共字段,并根据需要使用简单的属性。但是,如果您像 99% 的 C# 开发人员一样仅为项目内部使用而编写代码,那么为什么重新编译是一个大问题呢?您所做的任何其他更改都将需要重新编译,那又怎样呢?我最后一次检查是不是 1995 年了,我们有快速的计算机、快速的编译器和增量链接器,即使进行较大的重新编译,也不需要超过几分钟的时间,而且很长一段时间以来,我就无法再使用“我的代码正在编译”作为 在办公室中打架 的借口。
您无法对变量进行数据绑定:很好,当您需要这样做时,请将其转换为属性。
属性具有使它们更适合调试的功能,例如反射和设置断点:很好,当您需要使用其中一项功能时,请将其转换为属性。完成调试并准备发布后,如果您不再需要这些功能,请将其改回字段。
属性允许您在派生类中覆盖行为:很好,如果您正在制作一个基类,在其中您认为这种情况很可能发生,请将适当的成员转换为属性。如果您不确定,请将其保留为字段,稍后可以更改。是的,那可能需要一些重新编译,但又怎样呢?
总之,是的,有一些微不足道属性的合法用途,但除非你正在制作一个闭源库以供公开发布,否则当需要时将字段转换为属性很容易,而对公共字段的非理性恐惧仅是一些面向对象教条主义,我们最好摆脱它们。

1
为了结束我对公共字段的新发现的憎恶,我需要更好的错误信息。在Razor Pages中构建SelectList时,我无意中省略了作为我的<select>选择列表基础的类的自动属性。 SelectList构造函数顺利完成,但页面呈现结果是“对象未设置为对象的实例”,并出现一些混乱的反射错误。(我不愿承认那花费了我多少时间。)至少我现在知道数据绑定是问题所在... - Ian W

7

对我来说,不使用公共字段的绝对禁区是缺少 IntelliSense,无法显示引用:

输入图像描述

这对于字段是不可用的。

输入图像描述


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