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 = 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 ,就使用前面计算过的缓存值,可以看最后一行的打印结果。

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

时间: 2024-09-30 04:20:56

11.3.5 为 C# 实现延迟值的相关文章

11.4.2.1 使用延迟值进行缓存

11.4.2.1 使用延迟值进行缓存 应用程序中最重要的部分是,应用程序启动时执行的代码,找到指定目录中的所有文件,并创建有关每个文件信息的数组:这些信息包含文件名,和为了展示调整大小而计算的延迟值.清单 11.21 是为此而创建的数据结构. 清单 11.21 创建有关图片信息的集合 (F#) open System.IO open System.Drawing type ImageInfo = { Name : string; Preview :Lazy<Bitmap> }   [1] le

11.4 实用延迟值

11.4 实用延迟值 如果我们有一系列计算,可能要花很长时间,并且只在需要时才计算这个(些)值,这时,延迟值就很有用了.在这种情况下,可以得益于缓存,我们在上一节已经用 C# 实现过,把延迟值作为缓存,在需要时才填充. 延迟值的另一个重要用途,是表达一些概念时,很难以其他方式编程.我们将先Haskell 提供的几个例子,讨论有关延迟值的实际使用:Haskell 在每一处都使用延迟计算,这使它成为非常具有表现力的语言.

11.3.3 用函数模拟延迟计算

在F# 和C# 中计算顺序是提前的:作为给函数参数使用的表达式,在函数自身开始执行之前就计算好了.在C# 和F# 中,我们可以使用函数值模拟延迟计算,另外,F# 甚至有一个专门的关键字,支持延迟计算. 但首先,对于提前计算规则有一个例外,你肯定知道,并经常使用,但只是因为太常用,反而可能没有意识到它的特别.有些特定的C# 运算符,比如,逻辑或(||).逻辑与(&&).条件运算符(?:),以及空合并运算符(null-coalescing,??),能实现短路径(short-circuiting

C++11新特性(1) 右值引用

在C++中,左值(lvalue)是可以获取其地址的一个量.由于经常出现在赋值语句的左边,因此称之为左值.例如一个有名称的变量. 例如: int a=10; //a就是一个左值. 传统的C++引用,都是左值引用.例如:int &ra=a;将ra关联到a.这就是左值引用. C++11,新增了右值引用的概念.用&&代表右值引用. 首先我们来看一下什么叫做右值.可以说所有不是左值的量都是右值.例如文本,临时对象或者临时值(都是不能获取地址的量). 右值引用,就是一个对右值的引用.特别地,这

(译)C++11中的Move语义和右值引用

郑重声明:本文是笔者网上翻译原文,部分有做添加说明,所有权归原文作者! 地址:http://www.cprogramming.com/c++11/rvalue-references-and-move-semantics-in-c++11.html C++一直致力于生成快速的程序.不幸的是,直到C++11之前,这里一直有一个降低C++程序速度的顽症:临时变量的创建.有时这些临时变量可以被编译器优化(例如返回值优化),但是这并不总是可行的,通常这会导致高昂的对象复制成本.我说的是怎么回事呢? 让我们

C++11新特性之 rvalue Reference(右值引用)

右值引用可以使我们区分表达式的左值和右值. C++11引入了右值引用的概念,使得我们把引用与右值进行绑定.使用两个"取地址符号": int&& rvalue_ref = 99; 需要注意的是,只有左值可以付给引用,如: int& ref = 9; 我们会得到这样的错误: "invalid initialization of non-const reference of type int& from an rvalue of type int&q

11.3.1.2 Haskell 的延迟计算策略

在延迟计算策略(lazy evaluation strategy)中,函数的参数值,在函数调用时不会计算,直到后来用到这个值时才计算.我们回到前面的例子: TestAndCalculate(Calculate(10)); 在这里,Haskell 直接跳转到TestAndCalculate 函数主体.Haskell 会记住参数值的名字叫 num,如果在后面需要 num 值,就会运行 Calculate(10),然后,继续执行,得到 TestCondition的结果.如果这个函数返回true,表示需

C++11新特性应用--实现延时求值(std::function和std::bind)

说是延时求值,注意还是想搞一搞std::function和std::bind. 之前博客<C++11新特性之std::function>注意是std::function怎样实现回调函数. 如今就算是补充吧,再把std::bind进行讨论讨论. 何为Callable Objects? 就可以调用对象,比方函数指针.仿函数.类成员函数指针等都可称为可调用对象. 对象包装器 Function wrapper Class that can wrap any kind of callable eleme

[网络流24题#11] 航空路线 [网络流,最大权值最大流]

只需要把费用流的Spfa中的小于号改一下就好了,对于题目中要求要飞过去在飞回来,只需要一律把边的方向定为从顶点编号较小的向顶点编号较大的,把顶点1和n的边的容量定为2,其余边为1即可. #include <iostream> #include <algorithm> #include <cstdio> #include <cstdlib> #include <cstring> #include <cmath> #include <