首页 > 代码库 > 11.3.5 为 C# 实现延迟值

11.3.5 为 C# 实现延迟值

11.3.5 为 C# 实现延迟值

 

在 11.3.3 节,我们使用函数来表示 C# 中的延迟计算。我们刚才在 F# 中探讨了Lazy<T> 类型,它为计算过的值,添加了缓存功能。从Visual Studio 2010 开始,在核心的 .NET 库下的System.Lazy <T> 就有了这种类型,因此,我们不必自己实现。

清单 11.18 是简化的 Lazy<T> 类。代码在许多方面做了简化,它不是线程安全的,不处理任何异常,只表达了核心概念。

[

清单的序号终于正常了。

]

清单11.18 表示延迟值的类 (C#)

public class Lazy<T> {

 readonly Func<T> func;

 bool evaluated = false;   | 表示缓存的状态

  Tvalue;               |

 

 public Lazy(Func<T> func) {   [1]

   this.func = func;

  }

 public T Value {   [2]

   get {

     if (!evaluated) {

       value = http://www.mamicode.com/func(); | 计算值,修改状态

       evaluated = true;   |

     }

     return value;

    }

  }

}

public class Lazy {   <-- 在创建值时,启用类型推断

 public static Lazy<T> Create<T>(Func<T> func) {

   return new Lazy<T>(func);

  }

}

 

这个类的第一个重要部分是构造函数[1],它的参数为函数,并将其存储在只读字段里面;而这个函数没有任何参数,只在调用时,才计算这个值,所以,我们使用 Func<T> 委托。在非泛型类型中,还有一个静态的方法,让我们创建延迟值时,更容易使用 C# 的类型推断。

延迟值用一个标志来表示值是否已经计算过。注意,我们将使用泛型,因此,使用 null 值来表示不可能很容易,虽然,我们添加了约束,来强制 T 是引用类型,我们需要允许这种可能性,函数可能返回 null 作为计算值。

使用缓存值的大部分代码是在 Value 属性[2]的 getter 中。从用户的角度来看,这是类的第二个重要部分。它首先测试是否已进行计算过函数。如果已经做过,我们就可以使用前面计算过的值;如果还没有,就调用函数,并作标志,保证不会多次计算。

现在,我们看一段简单代码,看看是如何使用这个类型的:

 

var lazy = Lazy.Create(() => Foo(10));

Console.WriteLine(lazy.Value);              // Prints ‘Foo(10)‘ and ‘True‘

Console.WriteLine(lazy.Value);              // Prints only ‘True‘

 

如果尝试此代码,可以看到,其行为与 F# 版本完全相同。当创建延迟值时,我们给它一个函数:这时,Foo 方法不会调用。第一次调用Value,计算函数并调用 Foo;后序再调用 Value ,就使用前面计算过的缓存值,可以看最后一行的打印结果。

到目前为止,我们讨论延迟值的动机是实现或运算符的延迟版本的难点。在下一节,我们将看两个更复杂的实际使用延迟值的示例。

11.3.5 为 C# 实现延迟值