# C# 的高级特性
# 委托
委托是一种知道如何对方法进行调用的对象。
委托类型定义了一类可以被委托实例调用的方法。具体的说,它定义了方法的返回类型和参数类型。
delegate int Transformer (int x); |
# 用委托编写插件方法
委托变量可以在运行时指定一个目标方法,这个特性可用于编写插件方法。
public delegate int Transformer (int x); | |
class Util | |
{ | |
public static void Transform (int[] values, Transformer t) | |
{ | |
for (int i = 0; i < values.Length; i++) | |
values[i] = t (values[i]); | |
} | |
} | |
class Test | |
{ | |
static void Main() | |
{ | |
int[] values = { 1, 2, 3 }; | |
Util.Transform (values, Square); // Hook in the Square method | |
foreach (int i in values) | |
Console.Write (i + " "); // 1 4 9 | |
} | |
static int Square (int x) => x * x; | |
} |
Transform 方法是一个高阶函数(higher-order function),因为它是一个以函数为参数的函数(返回委托的方法也称为高阶函数)。
# 多播委托
所有的委托实例都拥有多播能力。这意味着一个委托实例可以引用一个目标方法,也可以引用一组目标方法。
SomeDelegate d = SomeMethod1; | |
d += SomeMethod2; | |
d -= SomeMethod1; |
// Suppose that you wrote a method that took a long time to execute. That method could regularly report progress to its caller by invoking a delegate. In this example, the HardWork method has a ProgressReporter delegate parameter, which it invokes to indicate progress: | |
public delegate void ProgressReporter (int percentComplete); | |
public class Util | |
{ | |
public static void HardWork (ProgressReporter p) | |
{ | |
for (int i = 0; i < 10; i++) | |
{ | |
p (i * 10); // Invoke delegate | |
System.Threading.Thread.Sleep (100); // Simulate hard work | |
} | |
} | |
} | |
// To monitor progress, the Main method creates a multicast delegate instance p, such that progress is monitored by two independent methods: | |
class Test | |
{ | |
static void Main() | |
{ | |
ProgressReporter p = WriteProgressToConsole; | |
p += WriteProgressToFile; | |
Util.HardWork (p); | |
} | |
static void WriteProgressToConsole (int percentComplete) | |
=> Console.WriteLine (percentComplete); | |
static void WriteProgressToFile (int percentComplete) | |
=> System.IO.File.WriteAllText ("progress.txt", | |
percentComplete.ToString()); | |
} |
# 实例方法目标和静态方法目标
# 泛型委托类型
# Fun 和 Action 委托
# 委托和接口
# 委托的兼容性
# 事件
# 标准事件模式
# 事件访问器
# 事件修饰符
# Lambda 表达式
Lambda 表达式是一种可以替代委托实例的匿名方法。编译器会立即将 Lambda 表达式转换为以下两种形式之一:
- 一个委托实例
- 一个类型为 Expression<TDelegate> 的表达式树(该表达式树将 Lambda 表达式内部的代码表示为一个可遍历的对象模型,因此 Lambda 表达式的解释可以延迟到运行时进行,请参见 8.10 节)
# 显示指定 Lambda 参数的类型
编译器通常可以根据上下文推断出 Lambda 表达式的类型,但是当无法推断的时候则必须显示指定每一个参数的类型。
void Foo<T> (T x) {} | |
void Bar<T> (Action<T> a) {} | |
// The following code will fail to compile, because the compiler cannot infer the type of x: | |
Bar (x => Foo (x)); // What type is x? | |
// We can fix this by explicitly specifying x’s type as follows: | |
Bar ((int x) => Foo (x)); | |
// This particular example is simple enough that it can be fixed in two other ways: | |
Bar<int> (x => Foo (x)); // Specify type parameter for Bar | |
Bar<int> (Foo); // As above, but with method group |
# 捕获外部变量
static void Main() | |
{ | |
int factor = 2; | |
Func<int, int> multiplier = n => n * factor; | |
Console.WriteLine (multiplier (3)); // 6 | |
} |
Lambda 表达式所引用的外部变量称为捕获变量。含有捕获变量的表达式称为闭包。
捕获变量会在真正调用委托时赋值,而不是在捕获时赋值:
int factor = 2; | |
Func<int, int> multiplier = n => n * factor; | |
factor = 10; | |
Console.WriteLine (multiplier (3)); // 30 |
Lambda 表达式也可以更新捕获变量的值:
int seed = 0; | |
Func<int> natural = () => seed++; | |
Console.WriteLine (natural()); // 0 | |
Console.WriteLine (natural()); // 1 | |
Console.WriteLine (seed); // 2 |
捕获变量的声明周期延伸到了和委托的生命周期一致。
static Func<int> Natural() | |
{ | |
int seed = 0; | |
return () => seed++; // Returns a closure | |
} | |
static void Main() | |
{ | |
Func<int> natural = Natural(); | |
Console.WriteLine (natural()); // 0 | |
Console.WriteLine (natural()); // 1 | |
} |
内部捕获变量是通过将变量 “提升” 为私有类的字段的方式实现的。当调用方法时,实例化该私有类,并将其生命周期绑定在委托实例上。
捕获迭代变量
当捕获 for 循环中的迭代变量时,C# 会认为该变量是在循环体外定义的。
Action[] actions = new Action[3]; | |
for (int i = 0; i < 3; i++) | |
actions [i] = () => Console.Write (i); | |
foreach (Action a in actions) a(); // 333 |
如果希望输出 012,那么需要将迭代变量指定到循环内部的局部变量中:
Action[] actions = new Action[3]; | |
for (int i = 0; i < 3; i++) | |
{ | |
int loopScopedi = i; | |
actions [i] = () => Console.Write (loopScopedi); | |
} | |
foreach (Action a in actions) a(); // 012 |
# Lambda 表达式和局部方法的对比
局部方法更加高效,因为它不需要间接使用委托(委托会消耗更多的 CPU 时钟周期并使用更多的内存),而且当它们访问局部变量的时候不需要编译器像委托那样将捕获变量放到一个隐藏类中。
但是,在许多情况下仍然需要使用委托。尤其是当需要调用高阶函数的时候,即使用委托作为参数的方法。