公共字段与自动属性

Posted

技术标签:

【中文标题】公共字段与自动属性【英文标题】:Public Fields versus Automatic Properties 【发布时间】:2010-11-13 22:19:54 【问题描述】:

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

【问题讨论】:

Difference between Property and Field in C# .NET 3.5+的可能重复 我已经将一个字段转换为一个属性,这样我就可以在设置器上设置一个断点 我倾向于将任何非私有的东西都设为属性,因为我意识到我必须将一个字段重构为属性会导致一些不必要的头痛。 Properties, fields, and methods. Oh My! 指出过去一直困扰着我的不兼容问题。 prop 代码 sn-p 可以快速创建属性。只需输入prop 然后按tab。 【参考方案1】:

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

这对于字段不可用。

【讨论】:

【参考方案2】:

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

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

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

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

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

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

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

【讨论】:

【参考方案3】:

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

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

public string Height  get; init; 

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

【讨论】:

【参考方案4】:

做了一些研究后我的观点

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

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

【讨论】:

【参考方案5】:

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

【讨论】:

【参考方案6】:

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

Properties vs. Public Variables

反射对变量和属性的工作方式不同,因此如果您依赖反射,则使用所有属性会更容易。 您不能对变量进行数据绑定。

将变量更改为属性是一项重大更改。例如:

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

【讨论】:

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

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

【讨论】:

【参考方案8】:

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

【讨论】:

为什么不直接说:封装。【参考方案9】:

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

【讨论】:

【参考方案10】:

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

【讨论】:

【参考方案11】:

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

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

在不违反约定的情况下获得额外的灵活性是我使用属性最重要的一点,并且在我真正需要灵活性之前,自动生成最有意义。

【讨论】:

【参考方案12】:

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

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

因为属性被实现为非常薄的方法,所以可以在读取和写入其值时设置断点。这使他们在田野上占了很大的优势。

【讨论】:

十年后,数据断点就在这里,至少对于.NET Core :)【参考方案13】:

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

【讨论】:

为什么不直接说:封装。 @Rainning 这更清楚了【参考方案14】:

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

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

【讨论】:

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

以上是关于公共字段与自动属性的主要内容,如果未能解决你的问题,请参考以下文章

公共字段自动填充

Mybatis-Plus实现公共字段自动赋值

十数据库公共字段处理

浅谈MyBatis-Plus学习之公共字段自动填充

Mybatis-plus 自动注入公共字段

SpringBoot-Mybatis_Plus学习记录之公共字段自动填充