384

我们经常被告知我们应该通过为类字段创建 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;
}
4

14 回答 14

186

在我前段时间遇到的一个相关问题中,有一个指向 Jeff 博客上的帖子的链接,解释了一些差异。

属性与公共变量

  • 反射在变量和属性上的工作方式不同,所以如果你依赖反射,使用所有属性会更容易。
  • 您不能对变量进行数据绑定。
  • 将变量更改为属性是一项重大更改。例如:

    TryGetTitle(out book.Title); // requires a variable
    
于 2009-07-25T01:08:37.027 回答
89

忽略 API 问题,我发现使用属性最有价值的事情就是调试。

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

因为属性是作为非常瘦的方法实现的,所以可以在读取和写入其值时设置断点。这使他们在田野上占了上风。

于 2009-07-25T01:22:58.123 回答
80

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

今天让它成为自动属性没什么,3 个月后意识到你想让它延迟加载,并在 getter 中放置一个空检查。如果您使用了一个字段,那么这充其量是一个重新编译更改,最坏的情况是不可能的,这取决于您的程序集依赖于谁和什么。

于 2009-07-25T01:07:24.593 回答
67

只是因为没有人提到它:你不能在接口上定义字段。所以,如果你必须实现一个定义属性的特定接口,自动属性有时是一个非常好的特性。

于 2010-09-04T01:53:38.717 回答
53

一个经常被忽视并且在任何其他答案中都没有提到的巨大差异:覆盖。您可以声明虚拟属性并覆盖它们,而您不能对公共成员字段执行相同操作。

于 2011-09-11T21:52:27.950 回答
12

一切都与版本控制和 API 稳定性有关。在版本 1 中没有区别 - 但后来,如果您决定需要在版本 2 中将此属性设为具有某种类型错误检查的属性,则无需更改 API - 除代码外,任何地方都无需更改代码财产的定义。

于 2009-07-25T01:09:21.643 回答
11

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

于 2013-10-11T13:14:13.777 回答
9

做一个字段没有错public。但请记住getter/setter,使用private字段创建不是封装。IMO,如果您不关心 a 的其他功能Property,您不妨制作它public

于 2010-09-04T02:20:13.170 回答
2

像这些琐碎的属性让我很难过。它们是最糟糕的货物崇拜,对 C# 中公共领域的仇恨需要停止。反对公共字段的最大理由是面向未来:如果您后来决定需要向 getter 和 setter 添加额外的逻辑,那么您将不得不对使用该字段的任何其他代码进行大量重构。在其他语言(如 C++ 和 Java)中确实如此,其中调用 getter 和 setter 方法的语义与设置和获取字段的语义非常不同。但是,在 C# 中,访问属性的语义与访问字段的语义完全相同,因此 99% 的代码应该完全不受此影响。

我见过的将字段更改为属性的一个示例实际上是源级别的重大更改,例如:

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

对此我不得不问,为什么 TF 你要传递其他类的字段作为参考?取决于这不是一个属性,这里似乎是真正的编码失败。假设您可以直接写入另一个您一无所知的类中的数据是不好的做法。制作自己的局部变量并book.Title从中设置。任何做这样的事情的代码都应该被打破。

我看到的其他反对它的论点:

  • 将字段更改为属性会破坏二进制兼容性,并且需要重新编译使用它的任何代码:如果您正在编写代码以作为闭源库进行分发,这是一个问题。在这种情况下,是的,请确保您的面向用户的类都没有公共字段并根据需要使用琐碎的属性。但是,如果您像 99% 的 C# 开发人员一样,编写代码纯粹是为了项目内部使用,那么为什么重新编译是一个大问题?几乎您所做的任何其他更改也需要重新编译,那么如果需要重新编译呢?上次我检查了一下,现在已经不是 1995 年了,我们拥有带有快速编译器和增量链接器的快速计算机,即使是更大的重新编译也不应该超过几分钟,而且自从我能够使用“我的代码正在编译”通过办公室的剑术
  • 您不能对变量进行数据绑定:太好了,当您需要这样做时,将其变为属性。
  • 属性具有使它们更适合调试的功能,例如反射和设置断点:太好了,您需要使用其中之一,将其变成属性。当您完成调试并准备发布时,如果您仍然不需要这些功能,请将其更改回字段。
  • 属性允许您覆盖派生类中的行为:很好,如果您正在创建一个您认为可能出现这种情况的基类,那么将适当的成员设置为属性。如果您不确定,请将其保留为字段,以后可以更改。是的,这可能需要重新编译,但又如何呢?

总而言之,是的,琐碎的属性有一些合法用途,但除非您正在为公开发布制作一个封闭源代码库,否则字段很容易在需要时转换为属性,并且对公共字段的非理性恐惧只是一些面向对象我们最好摆脱的教条。

于 2021-10-26T06:11:24.253 回答
1

如果您稍后决定通过与集合或数据库进行比较来检查标题是否唯一,您可以在属性中执行此操作,而无需更改任何依赖于它的代码。

如果您只使用公共属性,那么您的灵活性将降低。

在不违反合同的情况下额外的灵活性对我来说是使用属性最重要的,并且在我真正需要灵活性之前,自动生成最有意义。

于 2009-07-25T01:45:12.027 回答
1

你可以用 Fields 做的一件事,但不能用 Properties 做(或者以前不能......我稍后会谈到)是 Fields 可以被指定为readonly而属性不能。因此,字段为您提供了一种明确的方式来表明您的意图,即仅在对象实例化时(从构造函数中)设置一个变量,此后不应更改。是的,您可以将属性设置为具有私有设置器,但这只是说“不能从类外部更改”,这与“不能在实例化后更改”不同 - 您仍然可以在类内实例化后更改它。是的,您可以将属性的支持字段设置为只读,但这会将实例化后的尝试更改为运行时错误而不是编译时错误。所以只读字段做了一些属性不能做的有用的事情。

但是,随着 C# 9 的变化,我们得到了以下有用的属性语法:

public string Height { get; init; }

它说“这可以从类外部使用,但只能在对象初始化时设置”,因此 Fields 的只读优势消失了。

于 2020-10-29T12:52:17.687 回答
0

我发现非常有用的一件事以及所有代码和测试原因是,如果它是属性与字段,Visual Studio IDE 会向您显示属性而不是字段的引用。

于 2017-06-20T03:19:55.213 回答
0

对我来说,不使用公共字段的绝对破坏因素是缺乏 IntelliSense,显示参考:

在此处输入图像描述

这不适用于字段。

在此处输入图像描述

于 2021-11-08T08:13:07.207 回答
0

做了一些研究后我的观点

  1. 验证。
  2. 允许覆盖访问器以更改属性的行为。
  3. 调试目的。通过在访问器中设置断点,我们将能够知道属性更改的时间和内容。
  4. 我们可以只设置一个字段。例如,公共 set() 和私有 get()。这在公共领域是不可能的。

它确实给了我们更多的可能性和可扩展性。

于 2020-03-13T19:53:44.817 回答