C#成员变量初始化;最佳实践?

Posted

技术标签:

【中文标题】C#成员变量初始化;最佳实践?【英文标题】:C# member variable initialization; best practice? 【发布时间】:2010-09-22 19:52:21 【问题描述】:

在声明时初始化类成员变量是否更好

private List<Thing> _things = new List<Thing>();
private int _arb = 99;

还是在默认构造函数中?

private List<Thing> _things;
private int _arb;

public TheClass()

  _things = new List<Thing>();
  _arb = 99;

这仅仅是风格问题还是存在性能权衡,一种或另一种方式?

【问题讨论】:

***.com/questions/24551/…的可能重复 【参考方案1】:

在性能方面,没有真正的区别;字段初始值设定项被实现为构造函数逻辑。唯一的区别是字段初始值设定项发生在任何“base”/“this”构造函数之前。

构造函数方法可以与自动实现的属性一起使用(字段初始值设定项不能) - 即

[DefaultValue("")]
public string Foo get;set;
public Bar()  // ctor
  Foo = "";

除此之外,我更喜欢字段初始值设定项语法;我发现它可以使事情本地化 - 即

private readonly List<SomeClass> items = new List<SomeClass>();
public List<SomeClass> Items get return items;

我不必四处寻找它被分配的位置......

明显的例外是您需要执行复杂的逻辑或处理构造函数参数 - 在这种情况下,基于构造函数的初始化是可行的方法。同样,如果您有多个构造函数,则最好始终以相同的方式设置字段 - 所以您可能有如下 ctor:

public Bar() : this("") 
public Bar(string foo) Foo = foo;

编辑:作为旁注,请注意,在上面,如果有其他字段(未显示)具有字段初始化器,那么它们仅在调用base(...) 的构造函数中直接初始化 - 即public Bar(string foo) ctor .另一个构造函数运行字段初始化器,因为它知道它们是由this(...) ctor 完成的。

【讨论】:

我知道这是一篇旧帖子,但我有一个问题:“调用 base(...) 的构造函数”是什么意思?你 public Bar(string foo) Foo = foo; 似乎没有调用:base(),还是隐式发生?感谢您的帮助。 @Bruno 对于class,每个构造函数都有一个隐含的: base(),除非您添加更具体的内容——可以是: base(123, "abc"),也可以是: this(123, "abc") @Marc 那么是按顺序初始化吗(1)fieldInitializer(2)BaseConstructor(3)LocalConstructor【参考方案2】:

实际上,您演示的字段初始值设定项是一种方便的简写。编译器实际上将初始化代码复制到您为类型定义的每个实例构造函数的开头。

这有两个含义:首先,任何字段初始化代码都会在每个构造函数中重复,其次,您在构造函数中包含的用于将字段初始化为特定值的任何代码实际上都会重新分配字段。

所以性能方面,关于编译代码大小,最好将字段初始化器移到构造函数中。

另一方面,性能影响和代码“膨胀”通常可以忽略不计,而字段初始化器语法的重要好处是降低了您可能忘记在某个构造函数中初始化某些字段的风险。

【讨论】:

性能点仅在您重新分配值时适用(即它不适用于原始代码)。同样,“膨胀”问题(这是微不足道的)仅适用于调用 base(...) 的 ctor - 因此您可以使用私有 ctor(如发布的那样)避开这个问题 - 只有这个 ctor 会初始化字段。 你知道这种重新分配是否真的发生在实践中吗?如果不改变程序语义,编译器应该能够删除额外的初始化。【参考方案3】:

字段初始值设定项的一个主要限制是无法将它们包装在 try-finally 块中。如果在字段初始化器中抛出异常,则在之前的初始化器中分配的任何资源都将被放弃;没有办法阻止它。构造中的其他错误可以通过让受保护的基础构造函数通过引用接受 IDisposable 并将其指向自身作为其第一个操作来处​​理,如果笨拙的话。然后可以避免调用构造函数,除非通过工厂方法,如果发生异常,工厂方法将在部分创建的对象上调用 Dispose。如果主类构造函数在“走私”对新对象的引用后失败,这种保护将允许清理派生类初始化程序中创建的 IDisposables。不幸的是,如果字段初始化程序失败,则无法提供此类保护。

【讨论】:

【参考方案4】:

使用字段初始值设定项或创建 Init() 函数。将这些东西放入构造函数的问题在于,如果您需要添加第二个构造函数,最终会得到复制/粘贴代码(或者忽略它并得到未初始化的变量)。

我要么在声明的地方初始化。或者让构造函数调用 Init() 函数。

【讨论】:

请注意上面 Marc Gravell 的帖子中使用 :this() 链接构造函数。这比编写单独的 Init() 函数要好得多。【参考方案5】:

例如变量,主要是风格问题(我更喜欢使用构造函数)。对于静态变量,有一个 performance benefit 来初始化内联(当然,并非总是可行)。

【讨论】:

【参考方案6】:

这完全取决于你。 我经常内联初始化它们,因为当我真的不需要构造函数时,我不喜欢有一个构造函数(我喜欢小类!)。

【讨论】:

【参考方案7】:

在上面添加一点 - 在实现具有实现的类时,您总是有一个构造函数。如果您不声明一个,则默认讲师由编译器推断 [public Foo()];一个不带参数的构造函数。

我经常喜欢同时提供这两种方法。允许那些希望使用它们的构造函数,并允许字段初始化器用于您希望使用类/类型的简化或默认实现的情况。这增加了代码的灵活性。请记住,如果选择,任何人都可以使用默认字段初始值设定项...如果您提供多个构造函数,请务必手动声明它 - public Foo()

【讨论】:

以上是关于C#成员变量初始化;最佳实践?的主要内容,如果未能解决你的问题,请参考以下文章

3 类型,存储和变量1

Lambda 函数的最佳实践

C#类的初始化顺序

表达式树练习实践:C#值类型引用类型泛型集合调用函数

C#如何给变量取一个好的名字

C#如何给变量取一个好的名字