【问题标题】:Any reason to use auto-implemented properties over manual implemented properties?有什么理由使用自动实现的属性而不是手动实现的属性?
【发布时间】:2023-09-19 01:25:01
【问题描述】:

我了解 PROPERTIES 相对于 FIELDS 的优势,但我觉得使用 AUTO 实现的属性而不是 MANUAL 实现的属性并没有真正提供任何优势,只是让代码看起来更简洁一些。

我觉得使用起来更舒服:

    private string _postalCode;

    public string PostalCode
    {
        get { return _postalCode; }
        set { _postalCode = value; }
    }

代替:

public string PostalCode { get; set; }

主要是因为如果我想要执行任何类型的 get 和 set 自定义实现,我必须创建自己的属性,无论如何都要由私有字段支持。那么,为什么不从一开始就咬紧牙关,立即为所有属性赋予这种灵活性,以保持一致性呢?考虑到您在 Visual Studio 中所要做的就是单击您的私有字段名称,然后按 Ctrl+E,这真的不需要额外的一秒钟,您就完成了。如果我手动执行,那么我最终会出现不一致,其中有一些由私有字段支持的手动创建的公共属性,以及一些自动实现的属性。无论是全自动还是全手动,它始终保持一致,我感觉好多了。

这只是我吗?我错过了什么吗?我错了吗?我是否过于强调一致性?我总能找到关于 C# 特性的合法讨论,而且几乎所有事情都有利弊,但在这种情况下,我真的找不到任何人建议不要使用自动实现的属性。

【问题讨论】:

  • 你说你现在也可以这样做——因为将来你might必须这样做。为什么要浪费时间做一些容易出错的事情(无论如何,你最终可能会复制和粘贴),而你只能在需要时这样做?
  • 谢谢罗伯。我的理由是,使用 Visual Studio,我可以通过一个额外的按键创建手动实现的属性,因此无需复制/粘贴。如果有一天我确实需要创建自己的实现,我不会在自动实现的属性和手动实现的属性之间产生分歧,它是 100% 一个或 100% 另一个。通过从一开始就完全手动,我可以随时创建自己的实现,而无需自动实现某些属性,并且某些属性是手动的。不过,我明白你的意思。
  • 在这种情况下,我支持你坚持 100% 手动实现。在遇到许多手动 getter/setter 的错字错误后,我往往会产生偏见
  • 在 VS 2010 中(至少),您可以输入 prop 并按 Tab。这会产生一个自动实现的属性。

标签: c# properties field encapsulation


【解决方案1】:

除了简洁之外,它并没有给你任何额外的东西。如果您更喜欢更冗长的语法,那么请务必使用它。

使用 auto props 的一个好处是,它可以潜在地使您免于犯愚蠢的编码错误,例如不小心将错误的私有变量分配给属性。相信我,我以前做过!

你关于自动道具不是很灵活的观点是一个很好的观点。您唯一的灵活性是使用private getprivate set 来限制范围。如果您的 getter 或 setter 对它们有任何复杂性,那么 auto props 不再是一个可行的选择。

【讨论】:

  • 根据我的经验,公共属性使人们摆脱了 OOP 的思维方式。我现在正在进行的一个项目的对象有时仅初始化了一些属性,因此猜测该属性是否为空,并且代码库中有很多区域检查空值。我认为它们在某些情况下非常有用,在我强调的情况下,它们似乎是魔鬼。
  • @CodyEngel 不能保证通过访问器访问的对象也不为空。如果人们会编写带有属性的草率代码,那么他们也会编写没有属性的草率代码。
【解决方案2】:

有些人think that automatic properties can be somewhat evil 但除此之外他们只是语法糖。除了保存几行代码之外,您不会通过使用它们获得任何好处,并且您可能会为自己创造更多的工作(因为您想要进行一些检查或引发事件,所以以后必须手动实现它)。一致性在编程中非常有价值(恕我直言)。

【讨论】:

  • 啊,所以在某个地方对此进行了讨论 :) 感谢您提供链接!
【解决方案3】:

我不了解其他人,但我倾向于停下来思考我应该为我的变量函数命名,以便其他人能够理解我的代码。

所以当我使用 auto 实现的属性时,我只需暂停一次

当我需要一个支持字段时我必须暂停两次,所以它会减慢开发速度:)

我的做法是:

  1. 一开始就将其设为私有变量
  2. 如果需要,将其更改为公开自动实现。
  3. 如果我需要获取或设置一些代码,请将其更改为支持字段。

如果一个类的不同属性以不同的方式暴露出来,这并没有错。

【讨论】:

    【解决方案4】:

    您将失去控制的一件事是将支持字段指定为 NonSerialized 的能力,但在这种情况下为属性创建支持字段很容易。

    忘记了:如果您或您使用的任何产品对成员(即 WCF)执行反射,那么您将看到损坏的支持字段名称,而不是您创建的“漂亮”支持字段。

    如果您之前提供了对服务的访问权限,或者如果您在接收端反序列化为相同的类结构(即在 WCF 管道的两端使用相同的类),这可能非常重要。在这种情况下,您不一定能够反序列化,因为您可以保证支持字段名称是相同的,除非您与源代码共享相同的 DLL。

    再澄清一点:假设您有一个 Web 服务,它通过 WCF 向您创建的 silverlight 客户端公开您的一些业务对象。为了重用您的业务逻辑,您的 Silverlight 客户端添加对您的业务对象的源代码的引用。如果您有自动实现的属性,则无法控制支持字段名称。由于 WCF 序列化成员而不是属性,因此您无法确定从 WCF 服务传输到 silverlight 的对象是否会正确反序列化,因为支持字段名称几乎肯定会不匹配。

    【讨论】:

      【解决方案5】:

      不保证自动实现的属性在构建之间保持相同的支持字段名称。因此,理论上在一个程序集版本中序列化一个对象,然后在另一个程序集中重新序列化同一对象可能会导致重大更改。

      不太可能,但如果您试图保持将程序集“换出”新版本的能力,这是一个有效的问题。

      通过使用手动实现的属性,您可以保证支持字段永远不会更改(除非您专门更改它)。

      除了那个细微的差别之外,自动属性是一个普通属性,它使用支持字段自动实现。

      【讨论】:

        【解决方案6】:

        我看到使用自动属性的优势之一是;在调试应用程序时它不会进入不必要的 Get/Set 部分。我知道我们可以使用 Debugger Attributes 或 Step over 来避免相同的情况;但是,如果在大型应用程序上进行调试,大多数情况下会发生这种情况。

        【讨论】:

          【解决方案7】:

          我总能找到关于 C# 特性的合法讨论,而且几乎所有事情都有利弊,但在这种情况下,我真的找不到任何人建议不要使用自动实现的属性。

          我在今天的代码审查中遇到了这个问题,当我询问我的同行时,我们也无法达成共识。我不喜欢模棱两可,我想知道至少一个能回答你问题的热门问题:

          • 使用一种方式比另一种方式有性能提升或影响吗?

          就像要知道要舔多少次才能到达 Tootsie Roll Tootsie Pop 的中心,我决定“让我们找出答案”。

          让我们首先实际做一个苹果与苹果的比较。

          假设我们有两个类:

          public class C 
          {
              private int z;
              public int Z
              {
                  get { return z;}
              }
          }
          
          public class Q
          {
              public int Z { get; }
          }
          

          第一个类是手动后备存储,第二个是自动编译器生成的版本。

          让我们看看为每个生成的 IL。

          一、手动后备存储版本:

          // Fields
          .field private int32 z
          
          // Methods
          .method public hidebysig specialname 
              instance int32 get_Z () cil managed 
          {
              // Method begins at RVA 0x2050
              // Code size 12 (0xc)
              .maxstack 1
              .locals init (
                  [0] int32
              )
          
              IL_0000: nop
              IL_0001: ldarg.0
              IL_0002: ldfld int32 C::z
              IL_0007: stloc.0
              IL_0008: br.s IL_000a
          
              IL_000a: ldloc.0
              IL_000b: ret
          } // end of method C::get_Z
          
          .method public hidebysig specialname rtspecialname 
              instance void .ctor () cil managed 
          {
              // Method begins at RVA 0x2068
              // Code size 8 (0x8)
              .maxstack 8
          
              IL_0000: ldarg.0
              IL_0001: call instance void [System.Private.CoreLib]System.Object::.ctor()
              IL_0006: nop
              IL_0007: ret
          } // end of method C::.ctor
          
          // Properties
          .property instance int32 Z()
          {
              .get instance int32 C::get_Z()
          }
          

          现在让我们看看第二类的 IL:

          // Fields
          .field private initonly int32 '<Z>k__BackingField'
          .custom instance void [System.Private.CoreLib]System.Runtime.CompilerServices.CompilerGeneratedAttribute::.ctor() = (
              01 00 00 00
          )
          .custom instance void [System.Private.CoreLib]System.Diagnostics.DebuggerBrowsableAttribute::.ctor(valuetype [System.Private.CoreLib]System.Diagnostics.DebuggerBrowsableState) = (
              01 00 00 00 00 00 00 00
          )
          
          // Methods
          .method public hidebysig specialname 
              instance int32 get_Z () cil managed 
          {
              .custom instance void [System.Private.CoreLib]System.Runtime.CompilerServices.CompilerGeneratedAttribute::.ctor() = (
                  01 00 00 00
              )
              // Method begins at RVA 0x2071
              // Code size 7 (0x7)
              .maxstack 8
          
              IL_0000: ldarg.0
              IL_0001: ldfld int32 Q::'<Z>k__BackingField'
              IL_0006: ret
          } // end of method Q::get_Z
          
          .method public hidebysig specialname rtspecialname 
              instance void .ctor () cil managed 
          {
              // Method begins at RVA 0x2068
              // Code size 8 (0x8)
              .maxstack 8
          
              IL_0000: ldarg.0
              IL_0001: call instance void [System.Private.CoreLib]System.Object::.ctor()
              IL_0006: nop
              IL_0007: ret
          } // end of method Q::.ctor
          
          // Properties
          .property instance int32 Z()
          {
              .get instance int32 Q::get_Z()
          }
          

          忽略用于添加可调试属性的额外编译器生成代码,它不会添加明显的可执行代码,生成的代码似乎没有任何差异

          现在,您可能会争辩说您的问题没有得到解答,但请考虑...

          如果您曾经编写过参与绑定的属性,例如:

              private string name;
              public string Name 
              { 
                  get { return name; }
                  set { SetProperty (ref name, value);
              }
          

          那么后备存储就是要走的路。

          另一方面,使用 Visual Studio 编辑器中的“prop” 快捷方式来生成自动属性非常方便,而且比 all快得多> 输入 ;)

          所以到最后,对于锤子来说,一切看起来都像钉子。不要当锤子。

          【讨论】:

            最近更新 更多