笨拙的委托语法
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
共享和非共享的变量混合使用。
生成一个额外类,它包含外部变量,还生成另一个额外类,它包含内部变量和对第一个额外类的引用。根本上,包含一个捕获变量的每个作用域都有它自己的类型。