5.1向笨拙的委托语法说拜拜
class CSharp1Syntax { static void LogPlainEvent(object sender, EventArgs e) { Console.WriteLine ("LogPlain"); } static void LogKeyEvent(object sender, KeyPressEventArgs e) { Console.WriteLine ("LogKey"); } static void LogMouseEvent(object sender, MouseEventArgs e) { Console.WriteLine ("LogMouse"); } static void Main() { Button button = new Button(); button.Text = "Click me"; button.Click += new EventHandler(LogPlainEvent); button.KeyPress += new KeyPressEventHandler(LogKeyEvent); button.MouseClick += new MouseEventHandler(LogMouseEvent); Form form = new Form(); form.AutoSize = true; form.Controls.Add(button); Application.Run(form); } }
5.2方法组转换
C#1.0中,如果要创建一个委托实例,就必须同时制定委托类型和要执行的操作,C#2.0支持从方法组到一个兼容委托类型的隐式转换。
Thread t = new Thread(new ThreadStart(MyMethod));//C#1.0 Thread t = new Thread(MyMethod);//C#2.0
5.3协变性和逆变性
- 5.3.1委托参数的逆变性
class InferenceAndContravariance { static void LogPlainEvent(object sender, EventArgs e) { Console.WriteLine ("An event occurred"); } static void Main() { Button button = new Button(); button.Text = "Click me"; button.Click += LogPlainEvent; button.KeyPress += LogPlainEvent; button.MouseClick += LogPlainEvent; Form form = new Form(); form.AutoSize = true; form.Controls.Add(button); Application.Run(form); } }
- 5.3.2委托返回类型的协变性
class Covariance { delegate Stream StreamFactory(); static MemoryStream GenerateSampleData() { byte[] buffer = new byte[16]; for (int i = 0; i < buffer.Length; i++) { buffer[i] = (byte)i; } return new MemoryStream(buffer); } static void Main() { StreamFactory factory = GenerateSampleData; using (Stream stream = factory()) { int data; while ((data = stream.ReadByte()) != -1) { Console.WriteLine(data); } } } }
- 5.3.3不兼容的风险
class BreakingChange { delegate void SampleDelegate(string x); public void CandidateAction(string x) { Console.WriteLine("Snippet.CandidateAction"); } public class Derived : BreakingChange { public void CandidateAction(object o) { Console.WriteLine("Derived.CandidateAction"); } } static void Main() { Derived x = new Derived(); SampleDelegate factory = new SampleDelegate(x.CandidateAction);
//C#1.0会输出"Snippet.CandidateAction" 而C#2.0则输出"Derived.CandidateAction"
factory("test"); } }
5.4 使用匿名方法的内联委托操作
- 5.4.1从简单的的开始,处理一个参数
class SimpleAnonymousMethods { static void Main() { Action<string> printReverse = delegate(string text) //使用匿名方法创建Action<string> { char[] chars = text.ToCharArray(); Array.Reverse(chars); Console.WriteLine(new string(chars)); }; Action<int> printRoot = delegate(int number) { Console.WriteLine(Math.Sqrt(number)); }; Action<IList<double>> printMean = delegate(IList<double> numbers)//在匿名方法中使用循环 { double total = 0; foreach (double value in numbers) { total += value; } Console.WriteLine(total / numbers.Count); }; printReverse("Hello world");//和调用普通方法一样调用委托 printRoot(2); printMean(new double[] { 1.5, 2.5, 3, 4.5 }); } }
说到实现,我们在IL中为源代码中的每个匿名方法都创建了一个方法:这时编译器将在已知类(匿名方法所在的类)的内部生成一个方法,并使用创建委托实例时的操作,就像它是一个普通方法一样。
5.4.2匿名方法的返回值
class ReturnFromAnonymousMethod { static void Main() { Predicate<int> isEven = delegate(int x) { return x % 2 == 0; }; Console.WriteLine(isEven(1)); Console.WriteLine(isEven(4)); } }
class FileSorter { static void SortAndShowFiles(string title, Comparison<FileInfo> sortOrder) { FileInfo[] files = new DirectoryInfo(@"C:\").GetFiles(); Array.Sort(files, sortOrder); Console.WriteLine(title); foreach (FileInfo file in files) { Console.WriteLine(" {0} ({1} bytes)", file.Name, file.Length); } } static void Main() { SortAndShowFiles("Sorted by name:", delegate(FileInfo first, FileInfo second) { return first.Name.CompareTo(second.Name); } ); SortAndShowFiles("Sorted by length:", delegate(FileInfo first, FileInfo second) { return first.Length.CompareTo(second.Length); } ); } }
5.4.3 忽略委托参数
class IgnoredParametersAnonymousMethods { static void Main() { Button button = new Button(); button.Text = "Click me"; button.Click += delegate { Console.WriteLine("LogPlain"); }; button.KeyPress += delegate { Console.WriteLine("LogKey"); }; button.MouseClick += delegate { Console.WriteLine("LogMouse"); }; Form form = new Form(); form.AutoSize = true; form.Controls.Add(button); Application.Run(form); } }
5.5 匿名方法中的捕获变量
5.5.1定义闭包和不同类型的变量
- 闭包的基本概念:一个函数除了能提供给它的参数交互之外,还能同环境进行更大程度的互动。但这个定义过于抽象,为了真正理解它在C#2.0中的应用情况,还需理解另两个术语。
- 外部变量(outer variable)是指作用域(scope)内包括匿名方法的局部变量或参数(不包括ref和out参数)。在类的实例成员内部的匿名方法中,this引用也被认为是一个外部变量。
捕获的外部变量(captured outer variable)通常简称为捕获变量(captured variable),它是在匿名方法内部使用的外部变量。重新研究一下“闭包”的定义,其中所说的“函数”是指匿名方法,而与之交互的“环境”是指由这个变量捕获到的变量集。
void EnclosingMethod() { int outerVariable = 5;//1.外部变量 string capturedVariable = "captured"; //2.被匿名方法捕获的外部变量 if (DateTime.Now.Hour==23) { int normalLocalVariable = DateTime.Now.Minute; //3.普通方法的局部变量 Console.WriteLine(normalLocalVariable); } MethodInvoker x = delegate() { string anonLocal="local to anonymous method"; //4.匿名方法的局部变量 Console.WriteLine(capturedVariable + anonLocal); //捕获外部变量 }; x(); }
- normalLocalVariable 不是外部变量,因为它的作用域内没有匿名方法。它的行为和普通局部变量别无二致。
- anonLocal也不是外部变量,它是匿名方法的局部变量,但不是EnclosingMethod的局部变量。只有委托实例被调用后,它才会存在于一个栈中。
- outerVariable是一个外部变量,因为在它的作用域内声明了一个匿名方法,但是,匿名方法没有引用它,所以它未被捕捉。
- capturedVariable是一个外部变量,因为在它的作用域内声明了一个匿名方法,而且由于在5这个位置使用了该变量,所以它成为了一个被捕捉的变量。
5.5.2 捕获变量的行为
class CapturedVariableWalkthrough { static void Main() { string captured = "before x is created"; MethodInvoker x = delegate { Console.WriteLine(captured); captured = "changed by x"; }; captured = "directly before x is invoked"; x(); Console.WriteLine(captured); captured = "before second invocation"; x(); } }
在整个方法中,我们使用的始终是同一个Captured变量。
5.5.3 捕获变量到底有什么用处
- 简单地说,捕获变量能简化避免专门创建一些类来存储一个委托需要处理的信息
5.5.4 捕获变量的延长生存期
class CapturedVariableLifetimeExtension { static MethodInvoker CreateDelegateInstance() { int counter = 5; //编译器创建了一个额外的类来容纳变量, //CreateDelegateInstance方法拥有对该类的一个实例的引用,所以它能使用counter。 //另外,委托也有对该实例的一个引用。这个实例和其他实例一样都在堆上 MethodInvoker ret = delegate { Console.WriteLine(counter); counter++; }; ret(); return ret; } static void Main() { MethodInvoker x = CreateDelegateInstance(); x(); x(); } }
5.5.5 局部变量实例化
class MultipleCaptures { static void Main() { List<MethodInvoker> list = new List<MethodInvoker>(); for (int index = 0; index < 5; index++) { int counter = index * 10; //1.实例化counter list.Add(delegate { Console.WriteLine(counter);//2.打印并递增捕获的变量 counter++; }); } foreach (MethodInvoker t in list) { t();//3.执行全部5个委托实例。 } list[0]();//4.第一个委托执行3次。 list[0](); list[0](); list[1]();//5.第二个委托执行1次。 } }
- 由循环的初始部分声明的变量只被实例化一次。这很容易弄错!如果你想捕获循环变量在一次特定的循环迭代中的值,必须在循环内部引入另一个变量,并将循环变量的值复制给它,再捕捉那个新变量。
5.5.6 共享和非共享的变量混合使用
static void Main() { MethodInvoker[] delegates = new MethodInvoker[2]; int outside = 0; for (int i=0; i < 2; i++) { int inside = 0; delegates[i] = delegate { Console.WriteLine ("({0},{1})", outside, inside); outside++; inside++; }; } MethodInvoker first = delegates[0]; MethodInvoker second = delegates[1]; first(); //0,0 first(); //1,1 first(); //2,2 second();//3,0 second(); //4,1 }
5.5.7 捕获变量的使用和小结