公共字段与自动属性

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

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

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

在我不久前的相关的问题中,有一个链接到Jeff博客上的帖子,解释了一些差异。

属性vs.公共变量

  • 反射在变量和属性上的工作方式不同,因此如果依赖于反射,则更容易使用所有属性。
  • 你不能对变量进行数据绑定。
  • 将一个变量更改为属性是一个破坏性的更改。例如:

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

这都是关于版本控制和API稳定性。在版本1中没有区别-但后来,如果您决定在版本2中使此属性具有某种类型的错误检查,则不必更改API-除了属性的定义之外,在任何地方都不需要更改代码。

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

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

因为属性是作为非常薄的方法实现的,所以可以在其值的读写上设置断点。这让它们在田野上占据了很大的优势。

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

如果只使用公共属性,那么灵活性就会降低。

对我来说,使用属性时最重要的是在不破坏契约的情况下获得额外的灵活性,在我真正需要这种灵活性之前,自动生成是最有意义的。

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

将字段设置为public并没有错。但是记住,用private字段创建getter/setter并不是封装。在我看来,如果你不关心Property的其他特性,你不妨让它成为public

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

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

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

我的丈夫做了一些调查

  1. 验证。
  2. 允许重写访问器以更改属性的行为。
  3. 调试的目的。通过在访问器中设置断点,我们将能够知道属性何时发生变化以及发生了什么变化。
  4. 我们可以有一个现场布景。例如,public set()和private get()。对于公共字段,这是不可能的。

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

有一件事你可以用字段做,但不能用属性(或者过去不能…我马上就会讲到这一点),字段可以被指定为readonly,而属性则不能。因此,Fields给了您一种明确的方式来表明您的意图,即变量只在对象实例化时设置(从构造函数中设置),之后不应该更改。是的,你可以设置一个属性来拥有一个私有setter,但这只是说“不能从类外更改”,这与“不能在实例化后更改”是不一样的。-你仍然可以在类内部实例化后更改它。是的,您可以将属性的支持字段设置为只读,但这将使实例化后尝试将其更改为运行时错误而不是编译时错误。所以只读字段做了一些属性不能做的有用的事情。

然而,这在c# 9中发生了变化,我们得到了下面这些有用的属性语法:

public string Height { get; init; }

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

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

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

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

对此,我要问,为什么TF要传递其他类的字段作为引用?依赖于它不是一个属性似乎是这里真正的编码失败。假设您可以直接写入另一个类中的数据,而您对此一无所知,这是一种糟糕的实践。创建自己的局部变量,并从中设置book.Title。任何做这种事情的代码都应该被打破。

我还看到了其他反对的观点:

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

所以总的来说,是的,对于一些不重要的属性有一些合法的用途,但是除非你是在为公共发布制作一个闭源库,否则在需要的时候,字段很容易转换成属性,而对公共字段的非理性恐惧只是一些面向对象的教条,我们应该很好地摆脱它。

对我来说,不使用公共字段的绝对原因是缺乏智能感知,显示引用:

enter image description here

字段不可用。

enter image description here