向C#添加“懒惰”关键字的问题

我很想写这样的代码:

class Zebra { public lazy int StripeCount { get { return ExpensiveCountingMethodThatReallyOnlyNeedsToBeRunOnce(); } } } 

编辑:为什么? 我认为这看起来好于:

 class Zebra { private Lazy<int> _StripeCount; public Zebra() { this._StripeCount = new Lazy(() => ExpensiveCountingMethodThatReallyOnlyNeedsToBeRunOnce()); } public lazy int StripeCount { get { return this._StripeCount.Value; } } } 

第一次调用属性时,它将在get块中运行代码,之后只是从中返回值。

我的问题:

  1. 将这种关键字添加到库中会涉及哪些费用?
  2. 在什么情况下会有问题?
  3. 你会觉得这有用吗?

我并没有开始讨论这个问题,但是我很好奇这个function需要考虑什么样的考虑。

我很好奇这个function需要考虑什么样的考虑。

首先,我写了一个关于这个主题的博客,等等。 看我的旧博客:

http://blogs.msdn.com/b/ericlippert/

和我的新博客:

http://ericlippert.com

为语言devise的各个方面的许多文章。

其次,C#devise过程现在开放供公众查看,所以您可以亲自看看语言devise团队在审核新functionbuild议时所考虑的内容。 详情请参阅roslyn.codeplex.com。

将这种关键字添加到库中会涉及哪些费用?

这取决于很多东西。 当然,没有便宜,简单的function。 只有更便宜,更难的function。 一般来说,成本是涉及devise,指定,实施,testing,logging和维护function的成本。 还有更多的特殊成本,比如没有做出更好function的机会成本,或者select与我们可能要添加的未来function交互不良的function的成本。

在这种情况下,该特性可能只是简单地将“lazy”关键字作为使用Lazy<T>的语法糖。 这是一个非常简单的function,不需要很多花哨的句法或语义分析。

在什么情况下会有问题?

我可以想到一些会导致我推迟这个function的因素。

首先,这是没有必要的; 这只是一个方便的糖。 它并没有真正为语言增添新的力量。 好处似乎不值得的成本。

其次,更重要的是,它将一种特殊的懒惰融入了语言。 有一种以上的懒惰,我们可能会select错误的。

怎么会有一种以上的懒惰? 那么,想想如何实施。 属性已经是“懒惰”了,因为它们的值只有在属性被调用的时候才会被计算出来,但是你需要的不仅仅是这些; 你想要一个被调用一次的属性,然后这个值被下一次caching。 从本质上来说,“懒惰”是指一种记忆财产。 我们需要做什么保证? 有许多可能性:

可能性1:根本不是线程安全的。 如果您在两个不同的线程上调用属性“第一次”,任何事情都可能发生。 如果你想避免竞争条件,你必须自己添加同步。

可能性#2:线程安全,两个不同的线程对属性的两次调用都调用初始化函数,然后比赛看看谁填充了caching中的实际值。 大概这个函数会在两个线程上返回相同的值,所以这里额外的成本只是在额外的浪费。 但是caching是线程安全的,并不会阻塞任何线程。 (因为线程安全caching可以用低锁或无锁代码来编写。)

实现线程安全的代码是有代价的,即使它是低锁的代码。 这个成本可以接受吗? 大多数人写的是有效的单线程程序, 将线程安全的开销添加到每个懒惰属性调用是否正确?

可能性#3:线程安全,使得初始化函数只能被调用一次; caching中没有竞赛。 用户可能有一个隐含的期望,即初始化函数只被调用一次; 它可能是非常昂贵的,两个不同的线程调用可能是不可接受的。 实现这种懒惰需要完全同步,其中一个线程可能无限期阻塞,而另一个线程正在运行惰性方法。 这也意味着如果懒惰方法存在lockingsorting问题,那么可能会出现死锁。

这增加了这个function的成本,这个成本是由使用它的人(因为他们正在编写单线程程序)所承担的。

那么我们如何处理呢? 我们可以添加三个特性:“懒惰而不是线程安全”,“懒惰的线程安全与种族”和“懒惰的线程安全与阻塞和可能的死锁”。 而现在这个function变得更加昂贵和难以logging。 这产生了巨大的用户教育问题。 每当你给开发者一个这样的select,你就给他们提供一个编写可怕的错误的机会。

第三,这个function看起来很弱。 为什么懒惰只能用于财产? 这似乎可以通过types系统普遍适用:

 lazy int x = M(); // doesn't call M() lazy int y = x + x; // doesn't add x + x int z = y * y; // now M() is called once and cached. // x + x is computed and cached // y * y is computed 

如果存在一个更普遍的特征,那么我们就不要做小而弱的特征。 但是现在我们正在谈论真正严重的devise和实施成本。

你会觉得这有用吗?

亲自? 不是很有用。 我写了很多简单的低locking惰性代码大多使用Interlocked.Exchange。 (我不在乎懒惰的方法是否运行了两次,其中一个结果被丢弃;我懒惰的方法从来没有那么昂贵。)模式是直截了当的,我知道它是安全的,没有额外的对象分配给委托或锁,如果我有一些更复杂的东西,我总是可以使用Lazy<T>为我做的工作。 这将是一个小方便。

系统库已经有了一个你想要的类: System.Lazy<T>

我相信它可以被整合到语言中,但是正如Eric Lippert所说的那样,添加语言function并不是一件轻松的事情。 有很多事情要考虑,成本/收益比要非常好。 由于System.Lazy已经处理得很好,我怀疑我们很快就会看到这个。

你知道在.Net 4.0中添加的Lazy<T>类吗?

http://sankarsan.wordpress.com/2009/10/04/laziness-in-c-4-0-lazyt/

你试过/你是说这个吗?

 private Lazy<int> MyExpensiveCountingValue = new Lazy<int>(new Func<int>(()=> ExpensiveCountingMethodThatReallyOnlyNeedsToBeRunOnce())); public int StripeCount { get { return MyExpensiveCountingValue.Value; } } 

编辑:

在你的文章编辑之后,我会补充说你的想法绝对更优雅,但是仍然具有相同的function!

这不太可能被添加到C#语言中,因为即使没有Lazy<T> ,您也可以轻松地自行完成。

一个简单但不是线程安全的例子:

 class Zebra { private int? stripeCount; public int StripeCount { get { if (this.stripeCount == null) { this.stripeCount = ExpensiveCountingMethodThatReallyOnlyNeedsToBeRunOnce(); } return this.stripeCount; } } } 

如果您不介意使用后编译器,则CciSharp具有以下function :

 class Zebra { [Lazy] public int StripeCount { get { return ExpensiveCountingMethodThatReallyOnlyNeedsToBeRunOnce(); } } } 

看看Lazy<T>types。 另外,也可以问Eric Lippert关于如何在语言中添加这样的东西,他毫无疑问会有一个观点。