好的或不好的做法? 在getter中初始化对象
至少据我的同事说,我似乎有一种奇怪的习惯。 我们一直在一起开展一个小项目。 我写这些类的方式是(简化示例):
[Serializable()]
public class Foo
{
public Foo()
{ }
private Bar _bar;
public Bar Bar
{
get
{
if (_bar == null)
_bar = new Bar();
return _bar;
}
set { _bar = value; }
}
}
所以,基本上,我只在初始化任何字段时调用getter并且该字段仍然为空。 我想通过不初始化任何地方不使用的属性,这可以减少过载。
ETA:我这样做的原因是我的类有几个属性返回另一个类的实例,而这个属性又具有更多类的属性,依此类推。 调用顶级类的构造函数随后会调用所有这些类的所有构造函数,但并不总是需要它们。
除个人偏好外,是否有人反对这种做法?
更新:我已经考虑过关于这个问题的许多不同意见,我会支持我接受的答案。 但是,现在我已经对这个概念有了更好的理解,并且我可以决定何时使用它以及何时使用它。
缺点:
优点:
大多数缺点不适用于我目前的图书馆,但是我将不得不测试一下“微优化”是否实际上优化了任何东西。
最后更新:
好的,我改变了我的答案。 我最初的问题是这是否是一个好习惯。 我现在确信它不是。 也许我仍然会在当前代码的某些部分使用它,但不是无条件且绝对不是全部。 所以我会在使用它之前失去我的习惯并思考它。 感谢大家!
你在这里有一个 - 天真的 - “懒惰初始化”的实现。
简短的回答:
无条件地使用惰性初始化不是一个好主意。 它有它的地方,但必须考虑到这个解决方案的影响。
背景和解释:
具体实现:
让我们先看看你的具体样本,为什么我认为它的实现天真:
它违反了最小惊喜原则(POLS)。 当一个值被分配给一个属性时,预计返回这个值。 在你的实现中, null
不是这种情况:
foo.Bar = null;
Assert.Null(foo.Bar); // This will fail
foo.Bar
调用者可能会获得Bar
两个不同实例,其中一个实例不会与Foo
实例建立连接。 对该Bar
实例所做的任何更改都会自动丢失。 这是违反POLS的另一起案件。 当只访问一个属性的存储值时,它应该是线程安全的。 虽然你可能会认为这个类不是线程安全的 - 包括你的属性的getter - 你必须正确记录它,因为这不是正常情况。 此外,我们很快会看到这个问题的介绍是不必要的。
一般来说:
现在是时候查看一般的懒惰初始化了:
延迟初始化通常用于延迟构建花费很长时间的对象的构造,或者一旦完全构建就需要大量内存。
这是使用延迟初始化的非常有效的原因。
然而,这样的属性通常没有setter,它摆脱了上面指出的第一个问题。
此外,可以使用线程安全的实现 - 比如Lazy<T>
- 以避免第二个问题。
即使在实现懒惰属性时考虑这两点,以下几点也是这种模式的一般问题:
该对象的构造可能不成功,导致属性获取器出现异常。 这是POLS的又一次违规行为,因此应该避免。 即使是“开发类库的设计指南”中有关属性的部分,也明确指出属性获取者不应该抛出异常:
避免从属性获取器中抛出异常。
属性获取者应该是没有任何先决条件的简单操作。 如果一个getter可能抛出一个异常,可以考虑重新设计这个属性作为一个方法。
编译器自动优化会受到影响,即内联和分支预测。 有关详细解释,请参阅Bill K的答案。
这些观点的结论如下:
对于每个懒惰实施的单一财产,您应该考虑这些要点。
这意味着,这是一个每个案例的决定,不能被视为一般的最佳做法。
这种模式有其自己的位置,但实现类时不是最佳实践。 由于上述原因, 不应无条件使用它 。
在本节中,我想讨论其他人提出的一些观点,作为无条件地使用惰性初始化的参数:
连载:
EricJ在一个评论中表示:
一个可能序列化的对象在反序列化时不会调用它的构造器(取决于序列化器,但许多常见的行为就像这样)。 将初始化代码放在构造器中意味着您必须提供对反序列化的额外支持。 这种模式避免了特殊的编码。
这个论点有几个问题:
微观优化:你的主要观点是你只想在有人实际访问它们时才构建对象。 所以你实际上在谈论优化内存使用。
出于以下原因,我不同意这个观点:
我承认有时候这种优化是有道理的。 但即使在这些情况下,延迟初始化似乎也不是正确的解决方案。 有两个原因反对它:
这是一个很好的设计选择。 强烈建议用于库代码或核心类。
它被一些“延迟初始化”或“延迟初始化”所调用,并且它被所有人认为是一个很好的设计选择。
首先,如果您在类级别变量或构造函数的声明中进行初始化,那么当您的对象被构建时,就会产生创建可能永远不会使用的资源的开销。
其次,只有需要时才会创建资源。
第三,避免垃圾收集未使用的对象。
最后,处理属性中可能发生的初始化异常以及在类级别变量或构造函数的初始化期间发生的异常更容易。
这条规则有例外。
关于“get”属性中额外检查初始化的性能参数,它是无关紧要的。 初始化和处理对象比使用跳转的简单空指针检查更有效。
http://msdn.microsoft.com/en-US/library/vstudio/ms229042.aspx开发类库的设计指南
关于Lazy<T>
通用Lazy<T>
类是为海报所需要的而创建的,请参阅http://msdn.microsoft.com/en-us/library/dd997286(v=vs.100).aspx上的Lazy Initialization。 如果您的.NET版本较旧,则必须使用问题中说明的代码模式。 这种代码模式变得如此普遍,以至于Microsoft认为可以在最新的.NET库中包含一个类,以便更容易地实现该模式。 另外,如果你的实现需要线程安全,那么你必须添加它。
原始数据类型和简单类
Obvioulsy,你不会对基本数据类型或类似List<string>
简单类使用延迟初始化。
在评论懒惰之前
Lazy<T>
是在.NET 4.0中引入的,因此请不要再添加关于此类的其他评论。
在评论微观优化之前
当你建立图书馆时,你必须考虑所有的优化。 例如,在.NET类中,您会看到在整个代码中用于布尔类变量的位数组,以减少内存消耗和内存碎片,仅举两个“微优化”。
关于用户界面
您不会对用户界面直接使用的类使用延迟初始化。 上周我花了一天的好时间去除组合框的视图模型中使用的八个集合的延迟加载。 我有一个LookupManager
处理任何用户界面元素所需集合的延迟加载和缓存。
“二传手”
我从来没有使用set-property(“setters”)来处理任何延迟加载的属性。 因此,你永远不会允许foo.Bar = null;
。 如果你需要设置Bar
那么我会创建一个名为SetBar(Bar value)
而不是使用延迟初始化
集合
类集合属性在声明时总是初始化,因为它们不应该为null。
复杂类
让我重复一遍,对复杂类使用延迟初始化。 通常,设计不佳的课程。
最后
我从来没有说过要为所有班级或所有情况做到这一点。 这是一个坏习惯。
你是否考虑使用Lazy<T>
来实现这样的模式?
除了轻松创建延迟加载的对象外,您还可以在对象初始化时获得线程安全性:
正如其他人所说的那样,如果对象的构建时间非常耗费资源,或者需要一段时间才能加载对象,那么就会延迟加载对象。
链接地址: http://www.djcxy.com/p/5377.html