ChatGPT解决这个技术问题 Extra ChatGPT

公共字段与自动属性

我们经常被告知我们应该通过为类字段创建 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;
}
我已经将一个字段转换为一个属性,这样我就可以在设置器上设置一个断点
我倾向于将任何不是私有的东西都变成一个属性,因为我意识到我必须将一个字段重构为一个属性会导致一些不必要的头痛。 Properties, fields, and methods. Oh My! 指出过去一直困扰着我的不兼容问题。
prop 代码段可以快速创建属性。只需键入 prop,然后按 Tab。

C
Community

在我前段时间的 related question 中,有一个指向 Jeff 博客上的帖子的链接,其中解释了一些差异。

Properties vs. Public Variables

反射在变量和属性上的工作方式不同,所以如果你依赖反射,使用所有属性会更容易。

您不能对变量进行数据绑定。

将变量更改为属性是一项重大更改。例如:TryGetTitle(out book.Title); // 需要一个变量


“将变量更改为属性是一项重大更改。”这当然只适用于编写可重用库时,大多数开发人员都没有这样做。
此外,属性,甚至是自动属性,都可以是虚拟的,而字段则不能。因此,基类可以具有编译器为自动属性生成的简单支持字段实现,而派生类可以执行额外的验证或其他逻辑/计算。
此外,字段是变量,可以通过引用(refout 关键字)传递,而属性是一对访问器,不能通过引用传递。例如,使用 outbool success = TryGetMyTitle(out myBook.Title); 将适用于字段而不适用于属性。这是一个清楚的例子,说明为什么从字段到属性的更改是一项重大更改!
@KyleBaran 不,这没有多大意义,因为属性是一对访问器方法,而不是变量。通常要做的事情是声明一个局部变量(可能读取属性并将其值放入局部变量),将局部变量作为 ref/out 传递,然后将属性设置为局部变量的值然后有。但是随后被调用的方法本身并不访问该属性,而是访问您在那里创建的局部变量。
@theberserker 是的,尽管在 C# 6 中您可以执行 public int Foo { get; } ,这将创建一个具有只读支持字段的自动属性。
J
JaredPar

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

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

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


十年后,数据断点出现了,至少对于 .NET Core 来说:)
R
Rex M

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

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


我喜欢这个答案,因为它没有使用“反射”、“界面”或“覆盖”等词。 (“合同”太糟糕了)
M
MartinStettner

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


Z
Zaid Masud

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


为什么不直接说:封装。
将公共字段更改为属性不是封装。
R
Reed Copsey

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


为什么不直接说:封装。
@Rainning 这更清楚
A
Arnaldo

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


f
fastcodejava

创建字段 public 没有任何问题。但请记住,使用 private 字段创建 getter/setter 不是封装。 IMO,如果您不关心 Property 的其他功能,不妨将其设为 public


J
Joel Croteau

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

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

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

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

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

将字段更改为属性会破坏二进制兼容性,并且需要重新编译使用它的任何代码:如果您正在编写代码以作为闭源库进行分发,这是一个问题。在这种情况下,是的,请确保您的面向用户的类都没有公共字段并根据需要使用琐碎的属性。但是,如果您像 99% 的 C# 开发人员一样,编写代码纯粹是为了项目内部使用,那么为什么重新编译是一个大问题?几乎您所做的任何其他更改也需要重新编译,那么如果需要重新编译呢?上次我检查了一下,现在已经不是 1995 年了,我们拥有带有快速编译器和增量链接器的快速计算机,即使是更大的重新编译也不应该超过几分钟,而且自从我能够使用“我的代码正在编译”作为在办公室里争吵的借口。

您不能对变量进行数据绑定:太好了,当您需要这样做时,将其变为属性。

属性具有使它们更适合调试的功能,例如反射和设置断点:太好了,您需要使用其中之一,将其变成属性。当您完成调试并准备发布时,如果您仍然不需要这些功能,请将其更改回字段。

属性允许您覆盖派生类中的行为:很好,如果您正在创建一个您认为可能出现这种情况的基类,那么将适当的成员设置为属性。如果您不确定,请将其保留为字段,以后可以更改。是的,这可能需要重新编译,但又如何呢?

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


您是否知道将字段更改为属性会破坏程序集的二进制合同?您不能只 xcopy 部署这样一个新程序集。如果你从字段转到属性,每个人都必须重新编译它的代码。所以这真的是一个突破性的变化。
@ThomasMaierhofer 是的,这是我的第一个要点。
为了结束我新发现的对公共领域的仇恨,我需要更好的错误信息。在 Razor Pages 中构建 SelectList 时,我不小心省略了作为 <select> 选项列表基础的类的自动属性。 SelectList 构造函数很高兴地成功了,但是在一些混乱的反射错误中,页面呈现结果是“对象未设置为对象的实例”。(我不愿意承认这花费了我多少时间。)至少我现在知道数据绑定问题是……
J
James Black

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

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

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


J
Jinlye

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

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

public string Height { get; init; }

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


T
TrtlBoy

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


K
KunYu Tsai

做了一些研究后我的观点

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

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


t
typhon04

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

https://i.stack.imgur.com/HE9Eu.png

这不适用于字段。

https://i.stack.imgur.com/f8hBn.png