笨拙的委托语法
C#1中,先写好一连串事件处理程序,然后写new EventHandler。
#region 5-1 Button button = new Button(); button.Text = "Click me"; button.Click += new EventHandler(LogPlainEvent);//点击触发 button.KeyPress += new KeyPressEventHandler(LogKeyEvent); button.KeyPress += LogKeyEvent;//隐式转换 button.MouseClick += new MouseEventHandler(LogMouseEvent);//鼠标单击时触发 #endregion #region 5-1 static void LogPlainEvent(object sender, EventArgs e) { MessageBox.Show("LogPlain"); } static void LogKeyEvent(object sender, KeyPressEventArgs e) { MessageBox.Show("LogKey"); } static void LogMouseEvent(object sender, MouseEventArgs e) { MessageBox.Show("LogMouse"); } #endregion方法组转换
C#2支持从方法组到一个兼容类型的隐式转换。方法组就是一个方法名,它可以添加一个目标。
协变性和逆变性
委托参数的逆变性
Button button2 = new Button(); button2.Click += LogPlainEvent; button2.KeyPress += LogPlainEvent;//转换和逆变性 button2.MouseClick += LogPlainEvent; static void LogPlainEvent(object sender, EventArgs e)//处理所有事件 { MessageBox.Show("LogPlain"); }委托返回类型的协变性
#region 5-3演示委托返回类型的协变性 StreamFactory factory = GenerateSampleData;//利用协变性转换方法组 using (Stream stream = factory()) { int data; while ((data = stream.ReadByte()) != -1)//调用委托以获得Stream { Console.WriteLine(data); } } #endregion#region 5-3 static MemoryStream GenerateSampleData()//声明返回MemoryStream的方法 { byte[] buffer = new byte[16]; for (int i = 0; i < buffer.Length; i++) { buffer[i] = (byte)i; } return new MemoryStream(buffer); } #endregion不兼容风险
#region 5-4 public class Derived : Snippet { public void CandidateAction(object x) { Console.WriteLine("Derived.CandidateAction"); } } public class Snippet { public void CandidateAction(string x) { Console.WriteLine("Snippet.CandidateAction"); } } #endregion #region 5-4C#1和C#2的重大变化 Derived x = new Derived(); SampleDelegate factory = new SampleDelegate(x.CandidateAction); factory("text"); #endregion使用匿名方法的内联委托操作
#region 5-5将匿名方法用于Action<T>委托类型 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 }); #endregion逆变性不适用匿名方法:必须指定和委托类型完全匹配的参数类型。
在值类型中编写匿名方法时,不能在内部引用this,引用类型中则没有这个限制。
IL为源代码中的每个匿名方法都创建了一个方法:编译器将在已知类(匿名方法所在类)内部生成一个方法,并创建委托实例时操作
#region 5-6代码精简的极端例子 List<int> x = new List<int>(); x.Add(5); x.ForEach(delegate(int n) { Console.WriteLine(Math.Sqrt(n)); } ); x.ForEach(delegate(int n) { Console.WriteLine(Math.Sqrt(n)); } ); #endregion匿名方法的返回值
#region 5-7从匿名方法返回一个值 Predicate<int> isEven = delegate(int x) { return x % 2 == 0; }; Console.WriteLine(isEven(1)); #endregion编译器只需要检查所有返回类型都兼容于委托类型声明的返回类型
#region 5-8用匿名方法简单的排序文件 SortAndShowFiles("Sorted by name:", delegate(FileInfo f1, FileInfo f2) { return f1.Name.CompareTo(f2.Name); }); SortAndShowFiles("Sorted by Length:", delegate(FileInfo f1, FileInfo f2) { return f1.Length.CompareTo(f2.Length); }); #endregion #region 5-8 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); } } #endregion忽略委托参数
#region 5-9使用忽略了参数的匿名方法来订阅事件 Button button = new Button(); button.Text = "Click me"; button.Click += delegate { MessageBox.Show("LogPlain"); }; button.KeyPress += delegate { MessageBox.Show("LogKey"); }; button.MouseClick += delegate { MessageBox.Show("LogMouse"); }; #endregion匿名方法中捕获变量
定义闭包和不同类型的变量
闭包:一个函数除了能通过提供给它的参数交互之外,还能痛环境进行更大程度的互动
外部变量:指作用域内包括匿名方法的局部变量或参数。在类的实例成员的匿名方法中,this引用也被认为是一个外部变量。
捕获外部变量:在匿名方法内部使用的外部变量。
#region 5-10不同种类的变量和匿名方法的关系 void EnclosingMethod() { int outerVariable = 5;//外部变量 string capturedVariable = "captured";//被匿名方法捕获的外部变量 if (DateTime.Now.Hour == 23) { int normalLocalVariable = DateTime.Now.Minute;//普通方法的局部变量,不是外部变量,作用域中没有匿名方法 Console.WriteLine(normalLocalVariable); } MethodInvoker x = delegate() { string anonLocal = "local to anonymous method";//匿名方法的局部变量 Console.WriteLine(capturedVariable + anonLocal);//捕获外部变量 }; x(); } #endregion捕获变量的行为
被匿名方法捕获到的是变量,而不是创建委托类型时该变量的值。
#region 5-11从匿名方法内外访问一个变量 string captured = "before x is created"; MethodInvoker x = delegate//创建委托实例不会导致执行 { MessageBox.Show(captured);//directly before x is invoked captured = "changed by x"; }; captured = "directly before x is invoked"; x();//changed by x MessageBox.Show(captured); captured = "defore second invocation"; x();//defore second invocation #endregion捕获变量的用处
能简化避免专门创建一些类来储存一个委托需要处理的信息
捕获变量的延长生存期
#region 5-12 static MethodInvoker CreateDelegateInstance()//拥有对该类的一个实例引用 { int counter = 5; MethodInvoker ret = delegate//拥有对该类的一个实例引用 { MessageBox.Show(Convert.ToString(counter)); counter++;//用一个额外的类来捕获变量,捕获变量的实例 }; ret(); return ret; } #endregion #region 5-12捕获变量的生存期延长 MethodInvoker x = CreateDelegateInstance(); x(); x(); #endregion局部变量实例化
#region 5-13使用多个委托来捕捉多个变量实例 List<MethodInvoker> list = new List<MethodInvoker>(); for (int index = 0; index < 5; index++) { int counter = index * 10;//实例化counter list.Add(delegate//创建5个委托实例 { MessageBox.Show(Convert.ToString(counter));//直接打印index * 10结果都为50,初始化变量只被实例化一次 counter++; } ); } foreach (MethodInvoker t in list) { t();//执行5个委托实例 } list[0]();//1 list[0]();//2 list[0]();//3 list[1]();//11 #endregion共享和非共享的变量混合使用。
生成一个额外类,它包含外部变量,还生成另一个额外类,它包含内部变量和对第一个额外类的引用。根本上,包含一个捕获变量的每个作用域都有它自己的类型。