【问题标题】:Why ever use fields instead of properties?为什么要使用字段而不是属性?
【发布时间】:2011-01-11 02:33:36
【问题描述】:

我对 C# 还很陌生,我认为属性是一件很棒的事情。太棒了,事实上,我看不到使用字段的任何真正优势。即使对于私有字段,属性提供的灵活性和模块化似乎充其量可以为您省去严重的麻烦,最坏的情况是完全没有效果。

我可以看到字段的唯一优势是您可以内联初始化它们。但大多数时候,无论如何,您都希望在构造函数中初始化它们。如果你不使用内联初始化,有什么理由不一直使用属性吗?

编辑:有些人提出需要使用字段备份属性(显式或自动)。让我澄清一下我的问题:是否有任何理由使用字段除了备份属性?即,是否有任何时候SomeType someField;SomeType SomeProperty { get; set; } 更可取?

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

【问题讨论】:

  • 不,我认为这是一个非常好的问题,也确实非常微妙。绝对不同。
  • 这不是重复的。另一个问题问“A和B有什么区别”;这个人承认 A 和 B 之间的区别并问“A 是否会使 B 过时?”
  • 在 C# 6.0 中,自动属性将支持内联初始化和 readonly 关键字,这使得这个问题更加贴切。
  • @hypehuman 我知道内联初始化SomeType SomeProperty { get; } = new SomeType();,但readonly 关键字是什么意思?

标签: c# coding-style properties field


【解决方案1】:

通常,属性需要一个支持字段,除非它们是简单的 getter/setter“自动属性”。

所以,如果你只是在做......

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

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

但是,如果你正在做...

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

...您需要 _name 支持字段。

对于不需要任何特殊get/set逻辑的私有变量,是做私有自动属性还是只做一个字段,真的是一个判断调用。我一般做一个字段,然后,如果我需要它是protectedpublic,我会把它改成自动属性。

更新

正如 Yassir 所指出的,如果您使用自动属性,那么仍然有一个字段潜伏在幕后,您实际上不必输入它。所以,底线是:属性不存储数据,它们提供对数据的访问。字段是实际保存数据的内容。所以,即使你看不到它们,你也需要它们。

更新 2

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

是否有任何时候 SomeType someField;优于SomeType SomeProperty { get; set; }?

...想到的一件事:如果您有一个私有字段,并且(根据私有字段的约定)您将其称为 _name,这会向您和任何阅读您的代码的人发出信号,表明您正在直接工作与私人数据。另一方面,如果您将所有内容都设为属性,并且(根据属性约定)调用您的私有属性Name,那么现在您不能只查看变量并判断它是私有数据。因此,仅使用属性会删除一些信息。我没有尝试使用所有属性来判断这是否是关键信息,但肯定会丢失一些东西。

另一件更次要的事情是,public string Name { get; set; }private string _name 需要更多的输入(而且有点混乱)。

【讨论】:

  • 你应该提到编译器为每个自动属性创建一个字段
  • 我认为张贴者的观点是:即使您给出的示例中的 _name 字段也可能是私有的自动实现属性。从理论上讲,您实际上根本不需要了解这些字段,它们只是属性如何工作的实现细节,编译器可以将它们作为实现细节完全隐藏给开发人员,就像 CPU 寄存器一样。
  • @Mark,您在技术上是正确的,但是使用私有属性作为“支持属性”似乎违反了约定,这会使需要阅读或维护的人不清楚您的代码意图它。它还会产生命名问题。如果公共财产是Name,你会怎么称呼私人财产?如果你称它为_name_Name,其他人会阅读你的代码,可能会得出关于该变量是属性还是字段的错误结论。也就是说,也许有一天属性和字段之间的区别会消失。
  • @DanM:感谢您的回答。不过,我真正理解的是SomeType someField;SomeType SomeProperty { get; set; } 之间的区别。在幕后,SomeProperty 可能有自动实现的字段,但从程序员的角度来看,有什么理由更喜欢someField?我编辑了我的原始问题以澄清这一点。
  • @Dan:“也就是说,也许有一天属性和字段之间的区别会消失。”我也这么认为。但这可能需要几代人的时间。我们最近才摆脱了assembler C++。
【解决方案2】:

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

someObject.SomeMethod(ref otherObject.SomeProperty);

它不会编译。

【讨论】:

    【解决方案3】:

    属性是一件美妙的事情——但有与属性访问相关的开销。不一定是问题,但需要注意的事情。

    避免过度使用属性获取器和设置器

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

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

    【讨论】:

    • “一个非虚拟属性的getter或setter,除了字段访问之外不包含任何指令将被编译器内联”他所指的情况(私有自动实现的属性)将属于这种情况类别,因此没有性能影响。
    【解决方案4】:

    如果你想拥有readonly 的东西,你几乎必须使用一个字段,因为没有办法告诉自动属性生成一个只读字段。

    我经常这样做。

    人为的例子:

    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; } }
    }
    

    这意味着 Rectangle 内部的任何内容都不能在构造后改变宽度或高度。如果有人试图向编译器抱怨。

    如果我改为使用带有私有 setter 的自动属性,编译器不会保护我免受我自己的伤害。

    我看到的另一个原因是,如果不必公开一段数据(留在private),为什么要将其设为属性?

    【讨论】:

    • 很高兴知道这一点。为什么你不能public readonly int Width { get; set; }
    • 不知道。我只能猜测;也许它被削减以支持其他功能,或者他们没有看到需要。你得问问 Eric Lippert :)
    • 由于您只能在构造函数中(或在声明它们时)处理只读字段,所以我也无法想象声明只读属性的好语法是什么样的。
    • @Matthew:“为什么你不能做 public readonly int Width { get; set; }”我认为这是另一个很好的问题。 :) 我也想知道这个问题的答案。
    • C# 6.0 现在支持只读属性! object MyProp { get; } 这个属性可以内联(object MyProp { get; } = ...)或在构造函数中设置,但不能在其他地方设置(就像只读字段一样)。
    【解决方案5】:

    虽然我同意 David Basarab 声明中的“意图”:“没有理由公开公开字段”,但我想强调一点不同:

    我将上面 David 的引用修改为:“没有理由公开暴露字段......在一个类之外......除非通过有意识地选择将字段封装在 Properties 中,通过它严格控制访问.

    属性不仅仅是“附加到”C# 上的字段语法的“外衣”:它们是一种基本的语言功能,其设计理由包括:

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

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

    3. 接口设计无法定义 'fields : 但可以定义属性。

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

    1. 局部变量字段:什么状态是方法私有的,transient:局部变量通常只在方法主体的范围内有效,甚至与“窄生命周期”一样在'for循环之类的范围内。当然你也可以将方法中的参数变量视为“局部”。

    2. 类实例字段:什么状态是类私有的,对于类的每个实例都独立存在,但很可能需要在类的多个地方使用。

      李>
    3. 静态实例字段:什么状态仅是类的属性,与类的实例数量无关。

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

      一个。通过公共属性:这里的所有其他答案都很好地涵盖了这方面的所有方面

      b.通过索引器

      c。通过方法

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

    建议您查看:MSDN on 'Fields ... MSDN on Properties ... MSDN on Indexers

    【讨论】:

      【解决方案6】:

      我不明白您为什么要使用私有自动属性。有什么好处

      private int Count {get; set;}
      

      结束

      private int count
      

      【讨论】:

      • 可以将默认属性设置为虚拟,以便可以覆盖它,并且您可以独立控制 get 和 set 访问(例如,将 get 访问器设为 public,将 set 访问器设为受保护)。如果您一开始不需要这些,但以后可能需要它们怎么办?然后您可以从一个字段开始,稍后再使用一个属性,除非您通过反射访问字段或属性。所以......优势只是有时存在。
      • 还值得注意的是,如果您稍后因为需求发生变化而从字段转换为属性,那么任何引用该字段的内容也必须重新编译。
      【解决方案7】:

      没有理由公开公开字段。

      如果您公开了一个字段,则您无法更改信息的来源,从内联定义到配置文件,无需重构。\

      您可以使用字段来隐藏内部数据。我很少赞成这样做,我只在我做某事公开隐藏并在属性中使用它时才使用字段。 (即我没有使用自动属性生成)

      【讨论】:

      • 不仅如此,在将信息隐藏在具体类中方面,公开公开字段违反 OOP 范式...
      • 我认为这并不能真正回答他的问题。他想知道为什么在某些情况下建议使用字段而不是属性。
      • 没有理由公开字段的说法不正确;如果是这种情况,该功能将是非法的。公开公开一个领域有很多充分的理由。例如,构建一个可变结构以在与旧 COM 对象的互操作场景中使用。当对象的字段作为“ref”或“out”参数传递给旧的 COM 对象方法时,这一点尤其密切。
      • 在处理“ref”或“out”的使用时,我支持字段的使用。需要大量额外的代码来设置转换为本质上是 DTO 的东西。所以我只是将属性更改为字段。为什么要把事情复杂化?
      【解决方案8】:

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

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

      根据David's answer,如果您谈论的是 API,那么您是对的:您几乎不想将内部状态(字段)作为 API 的一部分。

      【讨论】:

        【解决方案9】:

        字段的语法比属性的语法要快得多,所以当使用字段(类私有)是安全的时候,为什么不使用它并节省额外的输入呢?如果自动实现的属性具有很好的简洁语法,并且您必须做额外的工作来创建一个普通的旧字段,那么人们可能会开始使用属性。此外,它现在是 C# 中的约定。这就是人们的想法,也是他们期望在代码中看到的。如果你做一些与平常不同的事情,你会迷惑所有人。

        但您可能会问,为什么字段的语法不会创建自动实现的属性而不是字段,因此您可以两全其美 - 无处不在的属性和简洁的语法。

        我们仍然需要显式字段的原因很简单:

        C# 1.0 没有我们现在拥有的所有这些好功能,因此字段是生活中的一个事实——没有它们你就无法真正生活。大量代码依赖于明显不同的字段和属性。它现在根本无法在不破坏大量代码的情况下进行更改。

        我也怀疑这会影响性能,但也许这可以通过抖动来解决。

        所以我们永远被字段所困,因为它们在那里并且它们采用了最好的语法,所以在安全的情况下使用它们是有意义的。

        【讨论】:

        • 为什么要一直拥有属性?!或者你的意思是在字段上的一个修饰符告诉它创建一个属性(不管它看起来像什么;))?
        • 为什么要为“这是一个成员”设置两种不同的语法? ;-)
        • 实际上,如果一切都是属性,则不需要修饰符。你甚至不需要说public int Foo { get; set; }。只是说public int Foo; 就可以了,因为如果一切都是属性,那将是安全的,并且代码更简洁。但显然这在 C# 中永远不会发生。拥有两个不同的东西“字段”和“属性”的决定是一成不变的,不能改变。
        • 我不太明白这将如何允许延迟加载或验证?
        • @Skurmedel:我不是说放弃属性的语法,我是说如果当前用于“字段”的语法实际上意味着“自动实现的属性”,那么你就不需要字段一点也不。我还说进行这种更改是完全不切实际的,至少对于 C# 而言。 :)
        【解决方案10】:

        字段和属性不可互换。我猜你说的是通过私有属性访问私有字段。我会在有意义的时候这样做,但在大多数情况下,这不是必需的。在大多数情况下,JIT 优化器将通过私有属性内联访问私有字段。并且将私有字段包装在私有属性中无论如何都不会被视为重大更改,因为私有成员不是您接口的一部分。

        就个人而言,我从不公开任何受保护/公共实例字段。尽管只要字段类型本身是不可变的,使用 readonly 修饰符公开一个公共静态字段通常是可以接受的。这在 SomeStruct.Empty 静态字段中很常见。

        【讨论】:

          【解决方案11】:

          正如其他人所指出的,无论如何,您都需要一个私有的属性支持字段。

          使用字段比使用属性还具有速度优势。在 99.99% 的情况下,这无关紧要。但在某些情况下可能会。

          【讨论】:

            【解决方案12】:

            速度。如果一个字段在模拟过程中被设置或读取数十亿次,那么您希望使用字段而不是属性来避免子例程调用的开销。尽可能符合 OO(DDD?),在这些情况下,我建议仅在专门用于表示某种“价值”的类中使用字段,例如人。逻辑应保持在最低限度。相反,请使用 personcreator 或 personservicer。

            但是,如果您有这些问题,那么您可能不是在编程 c++ 而不是 c#,不是吗?

            【讨论】:

              【解决方案13】:

              @Seth 提供了几个好的(部分)答案(字段性能更好,因此在私人环境中,您不妨在有意义的情况下使用它来为您带来好处),@Skurmedel(字段可以是只读的),@Jenk (字段可用于参考/输出)。但我想再补充一个:

              您可以使用简化的初始化语法来设置字段的值,但不能设置属性。即:

              private int x = 7;
              

              private int x { get; set; }
              
              // This must go in the constructor, sometimes forcing you to create 
              // a constructor that has no other purpose.
              x = 7;
              

              【讨论】:

              • C#6 现在可以实现这一点
              • 您现在可以使用private int x { get; set; } = 7;。我相信这就是加斯帕所说的。
              猜你喜欢
              • 2013-12-27
              • 2011-08-05
              • 2010-10-13
              • 2020-04-26
              • 1970-01-01
              • 1970-01-01
              • 1970-01-01
              • 2016-06-04
              • 2014-01-07
              相关资源
              最近更新 更多