[CLR via C#]17. 委托
delegate void StringProcessor(string input);
其中的StringProcessor是一个类型。
private void GetStringLength(object x){} //C#2.0以后认为一致
3.创建委托实例
StringProcessor proc1,proc2
//GetStringLength 实例方法
proc1= new StringProcessor(GetStringLength);
//GetString 静态方法
proc2 += GetString;
proc1("Hello World");
具体的示例代码:
namespace Program {
//定义委托
delegate void StringProcessor(string input); class Person
{
string name;
public Person(string name)
{
this.name = name;
} //定义与委托签名相同的"方法"
public void Say(string message)
{
Console.WriteLine(name+" say:" + message);
}
} class BackGround
{
//定义与委托签名相同的"静态方法"
public static void Note(string note)
{
Console.Write("{0}", note);
}
} class Program
{
static void Main(string[] args)
{
Person jon = new Person("Jom");
Person tom = new Person("Tom");
//创建委托实例(第一步)
StringProcessor proc1, proc2, proc3; //创建委托实例:赋值(第二部)
proc1 = new StringProcessor(jon.Say);
proc2 = new StringProcessor(tom.Say);
proc3 = BackGround.Note; //调用委托
proc1("Hello jon");
proc2("Hello tom");
proc3("note"); Console.ReadKey();
}
}
}
namespace Test
{
// 1.声明委托类型
internal delegate void Feedback(Int32 value); internal class Program
{
private static void Main(string[] args)
{
StaticDelegateDemo();
InstanceDelegateDemo();
ChainDelegateDemo1(new Program());
ChainDelegateDemo2(new Program()); } private static void StaticDelegateDemo()
{
Console.WriteLine("----- Static Delegate Demo -----");
Counter(, , null);
// 3.创建委托实例
Counter(, , new Feedback(Program.FeedbackToConsole));
Counter(, , new Feedback(FeedbackToMsgBox));
Console.WriteLine();
} private static void InstanceDelegateDemo()
{
Console.WriteLine("----- Instance Delegate Demo -----");
Program di = new Program();
// 3.创建委托实例
Counter(, , new Feedback(di.FeedbackToFile)); Console.WriteLine();
} private static void ChainDelegateDemo1(Program di)
{
Console.WriteLine("----- Chain Delegate Demo 1 -----");
// 3.创建委托实例
Feedback fb1 = new Feedback(FeedbackToConsole);
Feedback fb2 = new Feedback(FeedbackToMsgBox);
Feedback fb3 = new Feedback(di.FeedbackToFile); Feedback fbChain = null;
fbChain = (Feedback)Delegate.Combine(fbChain, fb1);
fbChain = (Feedback)Delegate.Combine(fbChain, fb2);
fbChain = (Feedback)Delegate.Combine(fbChain, fb3);
Counter(, , fbChain); Console.WriteLine();
fbChain = (Feedback)Delegate.Remove(fbChain, new Feedback(FeedbackToMsgBox));
Counter(, , fbChain);
} private static void ChainDelegateDemo2(Program di)
{
Console.WriteLine("----- Chain Delegate Demo 2 -----");
Feedback fb1 = new Feedback(FeedbackToConsole);
Feedback fb2 = new Feedback(FeedbackToMsgBox);
Feedback fb3 = new Feedback(di.FeedbackToFile); Feedback fbChain = null;
fbChain += fb1;
fbChain += fb2;
fbChain += fb3;
Counter(, , fbChain); Console.WriteLine();
fbChain -= new Feedback(FeedbackToMsgBox);
Counter(, , fbChain);
} private static void Counter(Int32 from, Int32 to, Feedback fb)
{
for (Int32 val = from; val <= to; val++)
{
// 如果指定了任何回调,就可以调用它
if (fb != null)
// 4.调用委托
fb(val);
}
} // 2.声明签名相同的方法
private static void FeedbackToConsole(Int32 value)
{
Console.WriteLine("Item=" + value);
} // 2.声明签名相同的方法
private static void FeedbackToMsgBox(Int32 value)
{
Console.WriteLine("Item=" + value);
} // 2.声明签名相同的方法
private void FeedbackToFile(Int32 value)
{
StreamWriter sw = new StreamWriter("Status", true);
sw.WriteLine("Item=" + value);
sw.Close();
}
}
}
deleget Object MyCallback(FileStream s);
完全可以构造该委托类型的一个实例,并和具有一下原型的一个方法绑定:
String SomeMethod(Stream s);
在这里,SomeMethod的返回类型(String)派生自委托的返回类型(Object);这种协变性是允许的。SomeMethod的参数类型(Stream)是委托的参数类型(FileStream)的基类;这种逆变性是运行部的。
Int32 SomeMethod(Stream s);//这是错误的
值类型和void之所以不支持协变性和逆变性,是因为它们的存储结构是变化的,而引用类型的存储结构始终是一个指针。
internal delegate void Feedback(Int32 value);
看到这行代码,编译器实际会像下面这样定义一个完整的类:
internal class Feedback: System.MulticastDelegate {
// 构造器
public Feedback(Object object, IntPtr method); // 这个方法和源代码指定的原型一样
public virtual void Invoke(Int32 value); // 以下方法实现了对回调方法的异步回调
public virtual IAsyncResult BeginInvoke(Int32 value, AsyncCallback callback, Object object);
public virtual void EndInvoke(IAsyncResult result);
}
编译器定义的类有4个方法:一个构造器、Invoke、BeginInvoke和EndInvoke。本节重点解释构造器和Invoke,BeginInvoke和EndInvoke看留到后面讲解。
字段 | 类型 | 说明 |
_target | System.Object | 当委托对象包装一个静态方法时,这个字段为null。当委托对象包装一个实例方法时,这个字段引用的是回调方法要操作的对象。换言之,这个字段指出了要传给实例方法的隐式参数this的值 |
_methodPtr | System.IntPtr | 一个内部的整数值,CLR用它来标识要回调的方法 |
_invocationList | System.Object | 该字段通常为null。构造一个委托链时,它可以引用一个委托数组。 |
Feedback fbStatic = new Feedback(Program.FeedbackToConsole);
Feedback fbInstance = new Feedback(new Program.FeedbackToFile());
fbStatic和fbInstance变量将引用两个独立的,初始化好的Feedback委托对象,如图17-2所示。
Boolean DelegateRefersToInstanceMethodOfType(MulticastDelegate d ,Type type) {
return ((d.Target != null) && d.Target.GetType() == type);
}
还可以写代码检查回调方法是否有一个特定的名称(比如FeedbackToMsgBox):
Boolean DelegateRefersToInstanceMethodOfName(MulticastDelegate d ,String methodName) {
return (d.Method.Name == methodName);
}
知道了委托对象如何构造并了解其内部结构之后,在来看看回调方法是如何调用的。为方便讨论,下面重复了Counter方法的定义:
private static void Counter(Int32 from, Int32 to, Feedback fb) {
for (Int32 val = from; val <= to; val++) {
// 如果指定了任何回调,就调用它们
if(fb != null ){
fb(val);
}
}
}
注意注释下方的那一行代码。if语句首先检查fb是否为null。如果不为null,下一行代码调用回调方法。
fb(val);
将生成以下代码,好像源代码本来就是这么写的:
fb.Invoke(val);
为了验证编译器生成的代码来调用委托类型的Invoke方法,可利用ILDasm.exe来检查生成的IL代码:
.method private hidebysig static void Counter(int32 from,
int32 'to',
class ConsoleTest.Feedback fb) cil managed
{
// 代码大小 41 (0x29)
.maxstack
.locals init ([] int32 val,
[] bool CS$$)
IL_0000: nop
IL_0001: ldarg.
IL_0002: stloc.
IL_0003: br.s IL_001d
IL_0005: nop
IL_0006: ldarg.
IL_0007: ldnull
IL_0008: ceq
IL_000a: stloc.
IL_000b: ldloc.
IL_000c: brtrue.s IL_0018
IL_000e: nop
IL_000f: ldarg.
IL_0010: ldloc.
IL_0011: callvirt instance void ConsoleTest.Feedback::Invoke(int32)
IL_0016: nop
IL_0017: nop
IL_0018: nop
IL_0019: ldloc.
IL_001a: ldc.i4.
IL_001b: add
IL_001c: stloc.
IL_001d: ldloc.
IL_001e: ldarg.
IL_001f: cgt
IL_0021: ldc.i4.
IL_0022: ceq
IL_0024: stloc.
IL_0025: ldloc.
IL_0026: brtrue.s IL_0005
IL_0028: ret
} // end of method Program::Counter
其实,完全可以修改Counter方法来显式调用Invoke方法,如下所示:
private static void Counter(Int32 from, Int32 to, Feedback fb) {
for (Int32 val = from; val <= to; val++) {
// 如果指定了任何回调,就调用它们
if(fb != null ){
fb.Invoke(val);
}
}
}
前面说过,编译器是在定义Feedback类时定义Invoke的。所以Invoke被调用时,它使用私有字段_target和_methodPtr在指定对象上调用包装好的回调方法。注意,Invoke方法的签名与委托的签名是匹配的。由于Feedback委托要获取一个Int32参数,并返回void,所以编译器生成的Invoke方法也要获取一个Int32参数,并返回void。
Feedback fbChain = null;
fbChain = (Feedback)Delegate.Combine(fbChain, fb1);
执行以上代码时,Combine方法会视图合并null和fb1。在内部,Combine直接返回fb1中的值,所以fbChain变量现在引用的就是fb1变量引用的那个委托对象。如图17-4所示:
fbChain = (Feedback)Delegate.Combine(fbChain, fb2);
在内部,Combine方法发现fbChain已经引用了一个委托对象,所以Combine会构造一个新的委托对象。这个新的委托对象对它的私有字段_target和_methodPtr进行初始化,具体值对目前讨论的来说并不重要。重要的是,_invocationList字段被初始化为引用一个委托对象数组。这个数组的第一个元素(索引为0)被初始化为引用包装了FeedbackToConsole方法的委托。数组的第二个元素(索引为1)被初始化为引用包装了FeedbackToMsgBox方法的委托。最后,fnChain被设为引用新建的委托对象,如图17-5所示:
fbChain = (Feedback)Delegate.Combine(fbChain, fb3);
同样的,Combine方法会发现fbChain已经引用了一个委托对象,于是又Combine会构造一个新的委托对象。这个新的委托对象对它的私有字段_target和_methodPtr进行初始化,具体值对目前讨论的来说并不重要。重要的是,_invocationList字段被初始化为引用一个委托对象数组。这个数组的第一个元素(索引为0)被初始化为引用包装了FeedbackToConsole方法的委托,数组的第二个元素(索引为1)被初始化为引用包装了FeedbackToMsgBox方法的委托,数组的第三个元素(索引为2)被初始化为引用包装了FeedbackToFile方法的委托。最后,fnChain被设为引用新建的委托对象。注意之前新建的委托以及_invocationList字段引用的数组已经被垃圾回收器回收了。如图17-6所示:
Counter(, , fbChain);
Counter方法内部的代码会在Feedback委托对象上隐式调用Invoke方法,这在前面已经讲过了。在fnChain引用的委托上调用Invoke时,该委托发现私有字段_invocationList不为null,所以会执行一个循环来遍历数组中的所有元素,并依次调用每个委托包装的方法。在本例中,首先调用的是FeedbackToConsole,然后是FeedbackToMsgBox,最后是FeedbackToFile。
以伪代码的方式,Feedback的Invoke的基本上是向下面这样实现的:
public void Invoke(Int32 value) {
Delegate[] delegateSet = _invocationList as Delegate[];
if (delegateSet != null) {
foreach(var d in delegateSet)
d(value);// 调用委托
}else{//否则,不是委托链
_methodPtr.Invoke(value);
}
}
注意,还可以使用Delegate公共静态方法Remove从委托链中删除委托,如下所示。
fbChain = (Feedback)Delegate.Remove(fbChain, new Feedback(FeedbackToMsgBox));
Remove方法被调用时,它扫描的第一个实参(本例是fbChain)所引用的那个委托对象内部维护的委托数组(从末尾向索引0扫描)。Remove查找的是其_target和_methodPtr字段与第二个实参(本例是新建的Feedback委托)中的字段匹配的委托。如果找匹配的委托,并且(在删除之后)数组中只剩下一个数据项,就返回那个数据项。如果找到匹配的委托,并且数组中还剩余多个数据项,就新建一个委托对象——其中创建并初始化_invocationList数组将引用原始数组中的所有数据项(删除的数据项除外),并返回对这个新建委托对象的引用。如果从链中删除了仅有的一个元素,Remove会返回null。注意,每次Remove方法调用只能从链中删除一个委托,它不会删除有匹配的_target和_methodPtr字段的所有委托。
public delegate Int32 Feedback (Int32 value);
如果这样定义,那么该委托的Invoke方法就应该向下面这样(伪代码形式):
public Int32 Invoke(Int32 value) {
Int32 result;
Delegate[] delegateSet = _invocationList as Delegate[];
if (delegateSet != null) {
foreach(var d in delegateSet)
result = d(value);// 调用委托
}else{//否则,不是委托链
result = _methodPtr.Invoke(_target,value);
}
return result;
}
1.C#对委托链的支持
为方便C#开发人员,C#编译器自动为委托类型的实例重载了+=和-=操作符。这些操作符分别调用了Delegate.Combine和Delegate.Remove。使用这些操作符,可简化委托链的构造。
Feedback fbChain = null;
fbChain += fb1;
fbChain += fb2;
fbChain += fb3;
public abstract class MulticastDelegate :Delegate {
// 创建一个委托数组,其中每个元素都引用链中的一个委托
public sealed override Delegate[] GetInvocationList();
}
GetInvocationList方法操作一个从MulticastDelegate派生的对象,返回一个有Delegate组成的数组,其中每一个引用都指向链中的一个委托对象。
public static class GetInvocationList
{
// 定义一个 Light 组件
private sealed class Light
{
// 该方法返回 light 的状态
public String SwitchPosition()
{
return "The light is off";
}
} // 定义一个 Fan 组件
private sealed class Fan
{
// 该方法返回 fan 的状态
public String Speed()
{
throw new InvalidOperationException("The fan broke due to overheating");
}
} // 定义一个 Speaker 组件
private sealed class Speaker
{
// 该方法返回 speaker 的状态
public String Volume()
{
return "The volume is loud";
}
} // 定义委托
private delegate String GetStatus(); public static void Go()
{
// 声明一个为null的委托
GetStatus getStatus = null; // 构造三个组件,将它们的状态方法添加到委托链中
getStatus += new GetStatus(new Light().SwitchPosition);
getStatus += new GetStatus(new Fan().Speed);
getStatus += new GetStatus(new Speaker().Volume); // 输出该委托链中,每个组件的状态
Console.WriteLine(GetComponentStatusReport(getStatus));
} // 该方法用户查询几个组件的状态
private static String GetComponentStatusReport(GetStatus status)
{ // 如果委托链为null,则不进行任何操作
if (status == null) return null; // 用StringBuilder来记录创建的状态报告
StringBuilder report = new StringBuilder(); // 获取委托链,其中的每个数据项都是一个委托
Delegate[] arrayOfDelegates = status.GetInvocationList(); // 遍历数组中的每一个委托
foreach (GetStatus getStatus in arrayOfDelegates)
{ try
{
// 获取一个组件的状态报告,将它添加到StringBuilder中
report.AppendFormat("{0}{1}{1}", getStatus(), Environment.NewLine);
}
catch (InvalidOperationException e)
{
// 在状态报告中生成一条错误记录
Object component = getStatus.Target;
report.AppendFormat(
"Failed to get status from {1}{2}{0} Error: {3}{0}{0}",
Environment.NewLine,
((component == null) ? "" : component.GetType() + "."),
getStatus.Method.Name, e.Message);
}
} // 返回遍历后的报告
return report.ToString();
}
}
执行结果为:
public delegate void TryCode(Object userData);
public delegate void WaitCallback(Object state);
public delegate void TimerCallback(Object state);
...
你发现上面几个委托的共同点了吗?它们其实全是一样的:这些委托类型的变量引用的方法都是获取一个Object,并且返回void。没有理由定义这么多委托类型,定义一个就好了!
public delegate void Action(); //这不是泛型
public delegate void Action<T>(T obj);
public delegate void Action<T1,T2>(T1 obj1,T2 obj2);
public delegate void Action<T1,T2,T3>(T1 obj1,T2 obj2,T3 obj3);
...
public delegate void Action<T1,...,T16>(T1 obj1,...,T16 obj16);
所以,.NET Framework现在提供17个Action委托,它们从无参数一直到最多16个参数。如果方法需要获取16个意思、上的参数,就必须定义自己的委托类型,但这种情况应该是极其罕见的。除了Action委托,.NET Framewoke还提供了17个Func函数,它们允许回调方法方法返回一个值:
public delegate TResult Func<TResult>();
public delegate TResult Func<T,TResult>(T1 arg);
public delegate TResult Func<T1,T2,TResult>(T1 arg1,T2 arg2);
...
public delegate TResult Func<T1,...,T16,TResult>(T1 arg1,...,T16 arg16);
建议尽量使用这些委托类型,而不是在代码中定义更多的委托类型。这样可以减少系统中的类型数目,同时简化编码。然而,如果需要使用ref或out关键字,以引用的方式传递一个参数,就可能不得不定义自己的委托:
delegate void Bar(ref Int32 z);
使用获取泛型实参和返回值的委托时,可利用逆变和协变,而且建议你总是利用这些功能,因为它们内有副作用,而且是你的委托适用于更多情形。
button1.Click += new EventHandle(button1_Click);
其中的button1_Click是一个方法,它看起来像下面这样:
void button1_Click(Object sender, EventArgs e) {
// 按钮单击后要做的事情....
}
第一行代码的思路是向按钮控件登记button1_Click方法的地址,以便在该按钮被单击时,可以调用方法。许多开发人员认为,仅仅为了指定button1_CLick方法的地址,就构造一个EventHandle委托对象,这显得有点不可思议。然而,构造EventHandle委托对象是CLR要求的,因为这个对象提供了一个包装器,可确保(被包装的)方法只能以类型安全的方式调用。这个包装器还支持调用实例方法和委托链。但是很多开发人员不想研究这些细节,更喜欢像下面这样的写代码:
button1_Click += button1_Click;
幸好,C#编译器为开发人员提供了一些用于处理委托的简化方法。后文描述的实际上可归为C#的语法糖(syntactical sugar);
public sealed class AClass
{
private static void CallbackWithoutNewingADelegateObject(){
ThreadPool.QueueUserWorkItem(SomeAsyncTask,);
} private static void SomeAsyncTask(Object o) {
Console.WriteLine(o);
}
}
这里,ThreadPool类的静态方法QueueUserWorkItem期望接受对一个WaitCallback委托对象的引用,委托对象中包装的是对SomeAsyncTask方法的一个引用。由于C#编译器能够自己进行推断,所以可以省略构造WaitCallback委托对象的代码,使整个代码的可读性更强,也更容易理解。当然,当代码编译时,C#编译器会生成IL代码来构建WaitCallback委托对象——我们只是在语法上得到了简化而已。
public sealed class AClass
{
private static void CallbackWithoutNewingADelegateObject(){
ThreadPool.QueueUserWorkItem(obj => Console.WriteLine(obj),);
}
}
注意,传给QueueUserWorkItem方法的第一个实参其实是一个lambda表达式。通过C# limbda表达式操作符=>,可以很容易地识别这种表达式。lambda表达式可在编译器预计需要一个委托的地方使用。编译器看到这个lambda表达式之后,会在类中自动创建一个新的私有方法。这个新方法成为匿名函数(anonymous function),因为方法的名称是编译器自动创建的,开发人员一般不知道这个名称。通过ILDasm.exe查看C#编译器将该方法命名为了<CallbackWithoutNewingADelegateObject>b__0,它获取一个Object参数,返回void.
internal sealed class AClass2
{
internal static void UsingLocalVariablesInTheCallbackCode(Int32 numToDo)
{ // 一些局部变量
Int32[] squares = new Int32[numToDo];
AutoResetEvent done = new AutoResetEvent(false); // 在其它线程上执行一系列任务
for (Int32 n = ; n < squares.Length; n++)
{
ThreadPool.QueueUserWorkItem(
delegate(Object obj)
{
Int32 num = (Int32)obj; // 耗时任务
squares[num] = num * num; // 如果是最后一个任务,则让主线程继续执行
if (Interlocked.Decrement(ref numToDo) == )
done.Set();
}, n);
} // 等待其他所有线程执行完毕
done.WaitOne(); // 显示任务
for (Int32 n = ; n < squares.Length; n++)
Console.WriteLine("Index {0}, Square={1}", n, squares[n]);
}
}
这个例子实际演示了C#如何简单的实现一个非常复杂的任务。以上方法定义了一个参数numToDo和两个局部变量aquares和done,而且lambda表达式的主题引用了这些变量。现在,想象以下lambda表达式主体中的代码在一个单独的方法中(事实上,也的确如此)。变量的值如何传递给这个单独的方法呢?唯一的方法就是定义一个新的辅助类,这个类要为我们打算传给回调代码的每一个值都定义一个字段。此外,回调代码还必须定义这个辅助类中的一个实例方法。然后,UsingLocalVariablesInTheCallbackCode方法必须构造辅助类的一个实例,用方法定义的局部变量的值来初始化这个实例中的字段。然后,构造绑定到辅助对象/实例方法的委托对象。
fb(item); //item为Int32类型
可以看出,在编码的时候,开发人员必须知道回调方法需要多少个参数,以及这些参数的具体类型。还好,开发人员几乎总是知道这些信息,所以像前面那样写代码是没有问题的。
public abstract class Delegate {
// 构造用于包装指定静态方法的一个"type"委托
public static Delegate CreateDelegate(Type type, MethodInfo method);
public static Delegate CreateDelegate(Type type, MethodInfo method,Boolean throwOnBindFailure); // 构造用于包装指定实例方法的一个"type"委托
public static Delegate CreateDelegate(Type type,Object firstArgument, MethodInfo method); //firstArgument指的是this
public static Delegate CreateDelegate(Type type,Object firstArgument, MethodInfo method,Boolean throwOnBindFailure); // 调用委托并传递参数
public Object DynamicInvoke(params Object[] args); }
所有CreateDelegate方法构造的都是从Delegate派生的一个类型新对象,具体类型由第一个参数type来标识。MethodInfo参数指出应该回调的方法;要用反射来获取这个值。如果希望委托包装一个实例方法,还要向CreateDelegate传递一个firstArgument参数,指定应作为this参数(第一个参数)传给实例方法的对象。最后,如果委托不能绑定到method参数指定的方法,CreateDelegate通常应该抛出一个异常。
internal static class DelegateReflection
{
// 下面是一些不同的委托定义
private delegate Object TwoInt32s(Int32 n1, Int32 n2);
private delegate Object OneString(String s1); public static void Go(String[] args)
{
if (args.Length < )
{
String fileName = Path.GetFileNameWithoutExtension(Assembly.GetEntryAssembly().Location); String usage =
@"Usage:" +
"{0}{1} delType methodName [Arg1] [Arg2]" +
"{0} where delType must be TwoInt32s or OneString" +
"{0} if delType is TwoInt32s, methodName must be Add or Subtract" +
"{0} if delType is OneString, methodName must be NumChars or Reverse" +
"{0}" +
"{0}Examples:" +
"{0} {1} TwoInt32s Add 123 321" +
"{0} {1} TwoInt32s Subtract 123 321" +
"{0} {1} OneString NumChars \"Hello there\"" +
"{0} {1} OneString Reverse \"Hello there\"";
Console.WriteLine(usage, Environment.NewLine, fileName);
return;
} // 将delType参数转换为一个委托类型
Type delType = Type.GetType(args[]);
if (delType == null)
{
Console.WriteLine("Invalid delType argument: " + args[]);
return;
} Delegate d;
try
{
// 将Arg1参数转换为一个方法
MethodInfo mi = typeof(Program).GetMethod(args[], BindingFlags.NonPublic | BindingFlags.Static); // 创建包装了静态方法的一个委托对象
d = Delegate.CreateDelegate(delType, mi);
}
catch (ArgumentException)
{
Console.WriteLine("Invalid methodName argument: " + args[]);
return;
} // 创建一个数组,其中只包含要通过委托对象传给方法的参数
Object[] callbackArgs = new Object[args.Length - ]; if (d.GetType() == typeof(TwoInt32s))
{
try
{
// 将String类型的参数转换为Int32类型的参数
for (Int32 a = ; a < args.Length; a++)
callbackArgs[a - ] = Int32.Parse(args[a]);
}
catch (FormatException)
{
Console.WriteLine("Parameters must be integers.");
return;
}
} if (d.GetType() == typeof(OneString))
{
// 只复制String参数
Array.Copy(args, , callbackArgs, , callbackArgs.Length);
} try
{
// 调用委托并显示结果
Object result = d.DynamicInvoke(callbackArgs);
Console.WriteLine("Result = " + result);
}
catch (TargetParameterCountException)
{
Console.WriteLine("Incorrect number of parameters specified.");
}
} // 这个回调方法获取2个Int32类型的参数
private static Object Add(Int32 n1, Int32 n2)
{
return n1 + n2;
} // 这个回调方法获取2个Int32类型的参数
private static Object Subtract(Int32 n1, Int32 n2)
{
return n1 - n2;
} // 这个回调方法获取1个String类型的参数
private static Object NumChars(String s1)
{
return s1.Length;
} // 这个回调方法获取1个String类型的参数
private static Object Reverse(String s1)
{
Char[] chars = s1.ToCharArray();
Array.Reverse(chars);
return new String(chars);
}
}
[CLR via C#]17. 委托的更多相关文章
- 【C#进阶系列】17 委托
委托主要是为了实 现回调函数机制,可以理解为函数指针(唯一不同的在于多了委托链这个概念). 然而用的时候可以这么理解,但是委托的内部机制是比较复杂的. 一个委托的故事 delegate void ra ...
- .NET via C#笔记17——委托
一.委托的内部实现 C#中的委托是一种类型安全的回调函数,假设有这样一个委托: internal delegate void Feedback(int value); 编译器会生成一个类: inter ...
- CLR之委托的揭秘(二)
杂谈 在开始真正的代码之前,分析一下上周的一些工作内容,发现自己在代码上还是有很多小毛病需要纠正和去更改的,首先之前一直疏于文档的整理,几乎很少去写文档,第二对于接口开发过程中缺少一定的严谨性,很多问 ...
- .NET面试题系列[7] - 委托与事件
委托和事件 委托在C#中具有无比重要的地位. C#中的委托可以说俯拾即是,从LINQ中的lambda表达式到(包括但不限于)winform,wpf中的各种事件都有着委托的身影.C#中如果没有了事件,那 ...
- C#中委托和事件的区别实例解析
这篇文章主要介绍了C#中委托和事件的区别,并分别以实例形式展示了通过委托执行方法与通过事件执行方法,以及相关的执行流程与原理分析,需要的朋友可以参考下 本文实例分析了C#中委托和事件的区别,分享给大家 ...
- 【CLR VIA C#】读书笔记
工作几年了才看,记录下笔记备忘. 章节 笔记 1.CLR的执行模型 公共语言运行时(Common Language Runtime,CLR) 源代码-->编译器检查语法和分析源代码-->托 ...
- 大话C#之委托
开篇先来扯下淡,上篇博客LZ在结尾说这篇博客会来说说C#中的事件.但是当LZ看完事件之后发现事件是以委托为基础来实现的,于是LZ就自作主张地在这篇博客中先来说说委托,还烦请各位看官见谅!!!另外关于委 ...
- C#进阶系列 ---- 《CLR via C#》
[C#进阶系列]30 学习总结 [C#进阶系列]29 混合线程同步构造 [C#进阶系列]28 基元线程同步构造 [C#进阶系列]27 I/O限制的异步操作 [C#进阶系列]26 计算限制的异步操作 ...
- C#中委托和事件的区别
大致来说,委托是一个类,该类内部维护着一个字段,指向一个方法.事件可以被看作一个委托类型的变量,通过事件注册.取消多个委托或方法.本篇分别通过委托和事件执行多个方法,从中体会两者的区别. □ 通过委托 ...
随机推荐
- System.Diagnostics.Debug和System.Diagnostics.Trace 【转】
在 .net 类库中有一个 system.diagnostics 命名空间,该命名空间提供了一些与系统进程.事件日志.和性能计数器进行交互的类库.当中包括了两个对开发人员而言十分有用的类——debug ...
- C#==>匿名方法 【转】
http://blog.csdn.net/gishero/article/details/5161826 1,匿名方法 C#为委托提供一种机制,可以为委托定义匿名方法,匿名方法没有名称,编译器会定指定 ...
- ECSHOP始终显示全部分类方法
商品分类树需要始终显示所有类别,默认的Ecshop的显示方式为在当前产品页面只显示当前的产品所在的同级及下级分类,这就导致当点开某个产品或者子分 类的时候全局的分类树就不见了. 其实修改的方法很简单. ...
- UE4在Android调用Project Tango
Project Tango应该说是Google一试水AR的设备,其中Project Tango主要二个功能,一个是获取深度信息,如MS的Kinect,有相当多的设备都有这个功能,二是第一人称相对定位, ...
- 【NLP】word2vec
http://blog.csdn.net/mytestmy/article/details/26969149?utm_source=tuicool&utm_medium=referral
- 初识WEB:输入URL之后的故事【转】
转载一篇文章,分析的是浏览器输入url后所执行的一系列操作!写得非常清晰易懂,分享给大家! 作者:Jesse 出处:http://jesse2013.cnblogs.com/ 本文版权归作者和博客园共 ...
- python load mat 并按变量名赋值
import numpy as np import scipy.io as io creat = locals() tmp = io.loadmat("all.mat") for ...
- seajs中spm压缩工具使用
seajs是个好东西,用起来很方便,但是她的压缩工具spm确不被网友看好,因为使用起来很麻烦,捯饬了一天多,终于勉强能压缩了,这里就简单记录一下. 按照地址:http://www.zhangxinxu ...
- Windows无法安装到GPT分区形式磁盘,怎么办?
有时候用原版系统镜像安装windows系统时,会提示“windows无法安装到这个磁盘.选中的磁盘采用GPT分区形式”,导致安装失败,下面就来讲解一下如何解决. 步骤阅读 百度经验:jingyan ...
- 【转载】shell中 dd 命令
转载自:http://blog.chinaunix.net/uid-24958038-id-3416169.html dd if=/dev/zero of=的含义是什么? 一.dd命令的解释 dd:用 ...