公共字段与自动属性

I. *_*edy 333 c# field properties class automatic-properties

我们经常被告知我们应该通过为类字段制作getter和setter方法(C#中的属性)来保护封装,而不是将字段暴露给外部世界.

但是很多时候,一个字段只是用来保存一个值,并且不需要任何计算来获取或设置.对于这些,我们都会这样做:

public class Book
{
    private string _title;

    public string Title
    {
          get{ return _title;  }
          set{ _title = value; }
    }
}
Run Code Online (Sandbox Code Playgroud)

好吧,我有一个坦白,我不忍心写所有这些(真的,它不必写它,它不得不看它),所以我去流氓和使用公共领域.

然后是C#3.0,我看到他们添加了自动属性:

public class Book
{
    public string Title {get; set;} 
}
Run Code Online (Sandbox Code Playgroud)

这更整洁,我很感激它,但是真的,除了创建一个公共领域之外还有什么不同?

public class Book
{
    public string Title;
}
Run Code Online (Sandbox Code Playgroud)

Mic*_*tum 168

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

属性与公共变量

  • 反射在变量与属性上的工作方式不同,因此如果依赖于反射,则更容易使用所有属性.
  • 您无法对变量进行数据绑定.
  • 将变量更改为属性是一个重大变化.例如:

    TryGetTitle(out book.Title); // requires a variable
    
    Run Code Online (Sandbox Code Playgroud)

  • 字段也是_variable_,可以通过引用传递(`ref`或`out`关键字),而属性是一对访问器,不能通过引用传递.例如`bool success = TryGetMyTitle(out myBook.Title);`使用`out`的`将使用字段而不使用属性.这是一个明显的例子,说明为什么从田地到财产的变化是一个突破性的变化! (27认同)
  • 此外,属性,甚至是自动属性,都可以是虚拟的,而字段则不能.因此,基类可以具有由编译器为auto-prop生成的简单后备字段实现,而派生类可以执行其他验证或其他逻辑/计算. (26认同)
  • "将变量更改为属性是一个重大变化." 这当然只适用于编写可重用的库,大多数开发人员都不会这样做. (23认同)
  • @KyleBaran不,它没有多大意义,因为属性是一对存取方法,而不是变量.通常要做的是声明一个局部变量(可能读取属性并将其值放入局部变量中),将局部变量作为`ref` /`out`传递,然后将属性设置为局部变量的值那么.但是,调用的方法本身不会访问该属性,它会访问您在那里创建的局部变量. (2认同)
  • 还没有人提到,`readonly` 只适用于字段,它与自动属性上的 `private set` 并不是一回事。 (2认同)
  • @theberserker是的,虽然在C#6中你可以做`public int Foo {get; }`这将创建一个带有只读后备字段的自动属性. (2认同)

Jar*_*Par 80

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

CLR调试器不支持数据断点(大多数本机调试器都支持).因此,无法在类的特定字段的读取或写入上设置断点.这在某些调试方案中非常有限.

由于属性是作为非常精简的方法实现的,因此可以在读取和写入其值时设置断点.这使他们在田野上占了一席之地.

  • 十年后,数据断点就在这里,至少对于 .NET Core 来说是这样:) (7认同)

Rex*_*x M 68

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

今天没有什么可以使它成为一个自动财产,并且在线下3个月意识到你想让它延迟加载,并在getter中进行空检查.如果你使用了一个字段,那么这是一个最好的重新编译更改,最坏的情况是不可能的,这取决于谁和什么依赖于你的程序集.

  • 我喜欢这个答案,因为它没有使用"反射","界面"或"覆盖"等字样.(关于'合同'太糟糕了) (7认同)

Mar*_*ner 62

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

  • @ zooone9243,+ MartinStettner:那是6个月前,从那时起我学到了很多东西.我把它拿回来:) (21认同)
  • @odyodyodys - 我不确定我是否同意这是糟糕的设计.请解释一下你的理由? (8认同)
  • @odyodyodys我同意zooone9243:Imp,从设计的角度来看,声明属性和声明getter/setter对之间没有区别(这是接口的常见做法). (4认同)

Zai*_*sud 45

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


Arn*_*ldo 10

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


Joe*_*eau 9

像这些微不足道的属性让我感到悲伤。它们是最糟糕的货物崇拜,对 C# 公共领域的仇恨需要停止。反对公共字段的最大论据是面向未来:如果您后来决定需要向 getter 和 setter 添加额外的逻辑,那么您将必须在使用该字段的任何其他代码中进行大量重构。在 C++ 和 Java 等其他语言中当然也是如此,其中调用 getter 和 setter 方法的语义与设置和获取字段的语义非常不同。然而,在 C# 中,访问属性的语义与访问字段的语义完全相同,因此 99% 的代码应该完全不受此影响。

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

    TryGetTitle(out book.Title); // requires a variable
Run Code Online (Sandbox Code Playgroud)

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

我见过的其他反对它的论点:

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

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


Ree*_*sey 8

这完全取决于版本控制和API稳定性.在版本1中没有区别 - 但是稍后,如果您决定需要在版本2中将此属性设置为某种类型的错误检查,则无需在任何地方更改API,无需更改代码,财产的定义.

  • @Rainning 这更清楚了 (2认同)

fas*_*ava 8

建立一个领域没有错public.但是记住getter/setterprivate字段创建并不是封装.IMO,如果你不关心a的其他功能Property,你也可以做到public.


typ*_*n04 7

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

在此输入图像描述

这不适用于字段。

在此输入图像描述