为什么要使用字段而不是属性?

47

我对C#比较陌生,但我认为属性是一件很棒的事情。事实上它们如此出色,以至于我看不到使用字段的任何真正优势。即使对于私有字段,似乎属性提供的灵活性和模块性最多只能避免严重的麻烦,最坏的情况下根本没有任何效果。

我唯一能看到字段的优势是您可以在行内初始化它们。但是大多数时间,您都想在构造函数中初始化它们。如果您没有使用行内初始化,那么除了用来支持属性,还有什么理由不一直使用属性呢?

编辑:有些人提出需要用字段(显式或自动地)支持属性。 让我们澄清我的问题:除了用来支持属性之外,是否有使用字段的任何理由?也就是说,SomeType someField; 何时比 SomeType SomeProperty { get; set; } 更可取?

编辑2:DanM、Skurmedel和Seth都给出了非常有用的答案。 我接受了DanM的答案,因为它是最完整的,但如果有人能够将他们的回答总结成一个答案,我也会很高兴接受。


2
在C#中,“字段”和“属性”的区别是什么? - user73993
9
不,我认为这是一个非常出色的问题,而且非常微妙。绝对不同。 - Mark Byers
11
这并非重复问题。其他问题询问“A和B的区别是什么”,而这个问题承认了A和B之间的差异,并问“是否A能够取代B?” - jyoungdev
5
在C#6.0中,自动属性将支持内联初始化和“readonly”关键字,这使得这个问题更加相关。 - hypehuman
2
@hypehuman 我知道内联初始化 SomeType SomeProperty { get; } = new SomeType();,但是你说的 readonly 关键字是什么意思? - Sebastian Werk
2
@SebastianWerk 哎呀,这个东西不存在。我可能在当时阅读公告时感到困惑了。只读属性是通过没有setter来隐式确定的;它们不使用显式的“readonly”关键字。 - hypehuman
13个回答

33

通常情况下,属性(properties)需要有后备字段(backing field),除非它们是简单的getter/setter“自动属性”(automatic properties)。

因此,如果你只需要...

public string Name { get; set; } // automatic property

...你不需要一个字段,我同意,没有理由去创建一个。

然而,如果你正在做...

public string Name
{
    get { return _name; }
    set 
    {
       if (value = _name) return;
       _name = value;
       OnPropertyChange("Name");
    }
}

对于不需要任何特殊get/set逻辑的私有变量,是一个判断使用私有自动属性或仅字段的问题。我通常使用字段,然后如果我需要它是protectedpublic,我会将其更改为自动属性。

更新

如Yassir所指出的,如果使用自动属性,则仍然有一个隐藏的字段,它只是您无需实际键入的东西。因此,重点是:属性不存储数据,它们提供对数据的访问。字段实际上保存数据。因此,即使您看不到它们,也需要它们。

更新2

关于您修改过的问题...

是否有时SomeType someField;比SomeType SomeProperty { get; set; }更可取?

......有一件事情值得一提:如果您有一个私有字段,并且(根据私有字段的惯例)称其为_name,那么这会向您和阅读您的代码的任何人发出信号,表示您正在直接处理私有数据。另一方面,如果将所有内容都设置为属性,并且(根据属性的惯例)命名您的私有属性为Name,那么现在您不能只查看变量并告诉它是私有数据。因此,仅使用属性会去除一些信息。我尚未尝试过使用所有属性来衡量是否关键信息被丢失,但确实会有一些东西被丢失。

另一个更小的问题是,public string Name { get; set; }需要更多输入(并且有点凌乱),而private string _name则需要更少的输入。


1
你应该提到编译器为每个自动属性创建一个字段。 - Hannoun Yassir
5
我认为这篇文章的观点是:即使在你所提供的例子中,“name”字段也可以是一个私有自动实现的属性。理论上,你实际上不需要了解这些字段,它们只是属性如何工作的实现细节,编译器可以将它们完全隐藏,就像CPU寄存器一样。 - Mark Byers
1
@DanM:感谢您的回答。但我真正想了解的是 SomeType someField;SomeType SomeProperty { get; set; } 之间的区别。在幕后,SomeProperty 可能有自动实现字段,但从程序员的角度来看,是否有理由更喜欢 someField?我编辑了我的原始问题以澄清这一点。 - Matthew
1
@Dan:“话虽如此,也许有一天属性和字段之间的区别会消失。”我也这么认为。但这可能需要几代人的时间。我们最近才刚刚戒掉了汇编语言 C++。 - Mark Byers
1
@Dan:“(根据属性的惯例)”我认为命名惯例更关注字段/属性是否为公共的,而不是它是否为属性。如果您有公共字段,它们可能也会有大写字母,而私有属性则不会。 - Mark Byers
显示剩余4条评论

17

尝试在使用ref/out参数时使用属性:

someObject.SomeMethod(ref otherObject.SomeProperty);

它无法编译。


11

属性是一件很棒的事情,但与属性访问相关的开销也是存在的。这不一定是一个问题,但需要注意。

避免过度使用属性的getter和setter

大多数人都没有意识到,在开销方面,属性的getter和setter与方法类似;它们之间主要的区别在于语法。一个不包含除了字段访问以外任何指令的非虚拟属性getter或setter将被编译器内联,但在许多其他情况下,这是不可能的。您应该仔细考虑使用属性;从类内部直接访问字段(如果可能的话),不要盲目地反复调用属性而没有将值存储在变量中。尽管如此,这并不意味着您应该使用公共字段!

来源:http://dotnet.sys-con.com/node/46342


13
编译器会将仅包含字段访问操作的非虚属性 getter 或 setter 内嵌,因此他所提到的情况(私有自动实现属性)将属于此类别,因此不会产生性能影响。 - Mark Byers

10

如果你想要让某个东西只读(readonly),你几乎必须使用字段(field),因为没有办法告诉自动属性(automatic property)生成一个只读字段(read-only field)。

我经常这样做。

举个假例:

class Rectangle
{
   private readonly int _width;
   private readonly int _height;

   public Rectangle(int width, int height)
   {
      _width = width;
      _height = height;
   }

   public int Width { get { return _width; } }
   public int Height { get { return _height; } }
}

这意味着在构造后,矩形内部的任何内容都不能更改其宽度或高度。如果尝试更改,编译器将会报错。

如果我使用具有私有setter的自动属性,编译器就无法保护我免受错误操作的影响。

另一个原因是,如果某个数据不需要被公开(保持private),为什么要将其作为属性?


1
这是很好知道的。为什么你不能做 public readonly int Width { get; set; } - Matthew
不知道。我只能猜测,也许它被削减了,以支持其他功能,或者他们认为没有必要。你需要问Eric Lippert :) - Skurmedel
由于您只能在构造函数(或声明它们时)上调整只读字段,因此我无法想象声明只读属性的良好语法会是什么样子。 - Skurmedel
@Matthew: "为什么你不能使用public readonly int Width { get; set; }" 我认为这是另一个非常好的问题。 :) 我也想知道答案。 - Mark Byers
12
C# 6.0现在支持只读属性!object MyProp { get; }该属性可以内联设置(object MyProp { get; } = ...)或在构造函数中设置,但不能在其他任何地方设置(就像只读字段)。 - Matthew
显示剩余2条评论

7

虽然我同意David Basarab的说法中所体现的“意图”:“没有理由公开暴露字段”,但我想加入一个略微不同的强调:

我会修改上面David的引用,使其变为:“除非通过有意识地选择封装字段的属性来严格控制访问,否则没有理由在类外部公开暴露字段。”

属性不仅仅是C#中添加到字段上的语法“面板”,它们是一种基本的语言特性,旨在实现以下目标:

  1. 控制类外部暴露和不暴露的内容(封装、数据隐藏)

  2. 允许在访问或设置属性时执行某些操作:这些操作最好在属性的“get”和“set”中表达,而不是被“提升”到外部定义的方法。

  3. 接口从设计上不能定义“字段”,但可以定义属性。

良好的面向对象设计意味着对“状态”做出有意识的选择:

  1. 局部变量字段:私有于方法的状态,通常只在方法体范围内有效,并且甚至在像“for循环”这样的范围内具有“狭窄的生命周期”。当然,您也可以将方法中的参数变量视为“局部”的。

  2. 类实例字段:私有于类的状态,并且对于类的每个实例具有独立的存在,但最可能需要在类的多个位置使用。

  3. 静态实例字段:状态仅作为类的属性存在,独立于类的实例数量。

  4. 故意和有意识地向“外部”公开的状态:关键思想是,在类和数据“消费者”之间插入至少一层间接层。 “暴露”的“反面”当然是有意隐藏(封装,隔离)实现代码。

    a. 通过公共属性:所有这些方面都在其他答案中得到了很好的涵盖

    b. 通过索引器

    c. 通过方法

    d. 公共静态变量通常位于实用程序类中,这些类通常是静态类。

建议您查看:关于“字段”的MSDN...关于属性的MSDN...关于索引器的MSDN


5

我不明白为什么要使用私有自动属性。有什么优势呢?

private int Count {get; set;}

结束

private int count

4
默认属性可以设置为虚拟,以便进行重写,并且您可以独立控制获取和设置访问(例如,将获取访问器设置为公共的,将设置访问器设置为受保护的)。如果一开始不需要这些,但以后可能会需要,那么可以从一个字段开始,然后稍后再使用属性,除非您通过反射访问字段或属性。因此... 这种优势只有在某些情况下才存在。 - jyoungdev
3
值得注意的是,如果由于需求变化而在以后从字段转换为属性,则任何引用该字段的内容也必须重新编译。 - Brandon Barkley

3

字段和属性不能互换使用。我猜你的意思是通过私有属性访问私有字段。在大多数情况下,当有意义时我会这样做,但大部分情况下并不必要。JIT优化器通常会内联访问私有字段的私有属性。而且,在私有成员不是接口的一部分的情况下,用私有属性包装私有字段也不被认为是一种破坏性变化。

个人而言,我永远不会公开任何受保护/公共实例字段。通常情况下,公开带只读修饰符的公共静态字段是可以接受的,只要该字段类型本身是不可变的。这经常出现在SomeStruct.Empty静态字段中。


3

正如其他人所指出的,无论如何您都需要为属性创建私有后备字段。

此外,使用字段而不是属性具有速度优势。在99.99%的情况下,这不会有影响。但在某些情况下可能会有所不同。


3

字段是存储状态的唯一位置。属性实际上只是一对带有特殊语法的方法,使它们可以根据使用方式映射到get或set方法:如果属性修改或访问状态,则该状态仍必须存储在字段中。

您并不总是看到这些字段。使用C# 3自动属性时,编译器会为您创建字段。但它仍然存在。此外,自动属性具有一些重要限制(例如,没有INotifyPropertyChanged支持,在setter中没有业务逻辑),这意味着它们通常不合适,您需要创建一个显式字段和手动定义的属性。

根据David的回答,如果您谈论API,则几乎永远不希望将内部状态(字段)作为API的一部分。


3
字段的语法比属性的语法更加简洁,因此当可以使用字段(仅限于类内部)时,为什么不使用它并节省额外的输入呢?如果自动实现的属性具有良好的简洁语法,并且您必须做额外的工作才能创建一个普通的字段,那么人们可能会开始使用属性。此外,这在C#中已经成为一种惯例。这是人们的思考方式,也是他们在代码中期望看到的。如果您做出与常规不同的事情,您将会让所有人感到困惑。
但是,您可能会问为什么字段的语法不会创建一个自动实现的属性而不是一个字段,这样您就可以兼得两者的优点——到处都是属性和简洁的语法。
我们仍然需要明确的字段有一个非常简单的原因:
C# 1.0没有我们现在拥有的所有这些好功能,因此字段是生活的一部分-您无法真正没有它们。大量的代码依赖于字段和属性可见地不同。现在无法改变它,否则会破坏大量的代码。
我还怀疑性能方面存在问题,但也许可以通过JIT解决。
因此,我们将永远被困在字段中,既然它们存在并且它们已经采用了最佳的语法,那么在安全的情况下使用它们是有意义的。

2
为什么你想要两种不同的语法来表示“这是一个成员”?;-) - Mark Byers
实际上,如果一切都是属性,你甚至不需要修饰符。你甚至不需要说 public int Foo { get; set; }。只要说 public int Foo; 就可以了,因为如果一切都是属性,那就是安全的,而且代码更简洁。但显然这在 C# 中永远不可能发生。将两个不同的东西“字段”和“属性”分开的决定已经定下来,不能改变。 - Mark Byers
我不太明白那怎么能实现延迟加载或验证? - Skurmedel
@Skurmedel:我并不是说要放弃属性的语法,我是说如果当前用于“字段”的语法实际上意味着“自动实现的属性”,那么你根本不需要字段。我还想说,至少对于C#来说,这种改变是完全不切实际的。 :) - Mark Byers
我明白你的意思,但如果你需要进行一些验证或类似操作,你会把东西存储在哪里?是在另一个属性中吗? - Skurmedel
显示剩余3条评论

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接