委托启用其他语言(如C ++,Pascal和Modula)已使用函数指针进行寻址的方案。但是,与C ++函数指针不同,委托是完全面向对象的,与成员函数的C ++指针不同,委托封装了对象实例和方法。
委托声明定义了从类派生的类System.Delegate
。委托实例封装了一个调用列表,该列表是一个或多个方法的列表,每个方法都被称为可调用实体。对于实例方法,可调用实体由该实例上的实例和方法组成。对于静态方法,可调用实体仅包含一个方法。使用适当的参数集调用委托实例会导致使用给定的参数集调用每个委托的可调用实体。
委托实例的一个有趣且有用的属性是它不知道或不关心它封装的方法的类; 重要的是这些方法与委托的类型兼容(委托声明)。这使得委托完全适合“匿名”调用。
委托声明
一个delegate_declaration是type_declaration(类型声明,声明一个新的委托类型)。
1 delegate_declaration 2 : attributes? delegate_modifier* 'delegate' return_type 3 identifier variant_type_parameter_list? 4 '(' formal_parameter_list? ')' type_parameter_constraints_clause* ';' 5 ; 6 7 delegate_modifier 8 : 'new' 9 | 'public' 10 | 'protected' 11 | 'internal' 12 | 'private' 13 | delegate_modifier_unsafe 14 ;
同一修饰符在委托声明中多次出现是编译时错误。
所述new
改性剂只允许在另一种类型的,在这种情况下,它指明这种委托隐藏同名一个继承的成员,如在描述中声明委托新的改性剂。
在public
,protected
,internal
,和private
修饰符控制委托类型的可访问性。根据委托声明发生的上下文,可能不允许某些修饰符(声明的可访问性)。
委托的类型名称是标识符。
可选的formal_parameter_list指定委托的参数,return_type指示委托的返回类型。
可选的variant_type_parameter_list(Variant类型参数列表)指定委托本身的类型参数。
委托类型的返回类型必须是void
或输出安全(方差安全)。
委托类型的所有形式参数类型必须是输入安全的。此外,任何out
或ref
参数类型也必须是输出安全的。请注意out
,由于底层执行平台的限制,甚至参数都需要输入安全。
C#中的委托类型是名称等价的,在结构上不等同。具体而言,具有相同参数列表和返回类型的两种不同委托类型被视为不同的委托类型。但是,两个不同但结构上等效的委托类型的实例可以比较为相等(委托相等运算符)。
1 delegate int D1(int i, double d); 2 3 class A 4 { 5 public static int M1(int a, double b) {...} 6 } 7 8 class B 9 { 10 delegate int D2(int c, double d); 11 public static int M1(int f, double g) {...} 12 public static void M2(int k, double l) {...} 13 public static int M3(int g) {...} 14 public static void M4(int g) {...} 15 }
这些方法A.M1
和B.M1
与双方委托类型兼容D1
和D2
,因为它们具有相同的返回类型和参数列表; 但是,这些委托类型是两种不同的类型,因此它们不可互换。的方法B.M2
,B.M3
以及B.M4
与委托类型不兼容D1
和D2
,因为他们有不同的返回类型或参数列表。
与其他泛型类型声明一样,必须提供类型参数以创建构造的委托类型。通过为委托声明中的每个类型参数替换构造的委托类型的相应类型参数,来创建构造的委托类型的参数类型和返回类型。生成的返回类型和参数类型用于确定哪些方法与构造的委托类型兼容。例如:
1 delegate bool Predicate<T>(T value); 2 3 class X 4 { 5 static bool F(int i) {...} 6 static bool G(string s) {...} 7 }
该方法X.F
与委托类型兼容,Predicate<int>
并且该方法X.G
与委托类型兼容Predicate<string>
。
声明委托类型的唯一方法是通过delegate_declaration。委托类型是派生自的类类型System.Delegate
。委托类型是隐式的sealed
,因此不允许从委托类型派生任何类型。也不允许从中派生非委托类类型System.Delegate
。请注意,System.Delegate
它本身不是委托类型; 它是一个类类型,从中派生所有委托类型。
C#为委托实例化和调用提供了特殊语法。除了实例化之外,任何可以应用于类或类实例的操作也可以分别应用于委托类或实例。特别是,可以System.Delegate
通过通常的成员访问语法访问该类型的成员。
由委托实例封装的方法集称为调用列表。当从单个方法创建委托实例(委托兼容性)时,它封装该方法,并且其调用列表仅包含一个条目。但是,当组合两个非null委托实例时,它们的调用列表将按照左操作数和右操作数的顺序连接,以形成一个新的调用列表,其中包含两个或多个条目。
使用二进制+
(加法运算符)和+=
运算符(复合赋值)组合委托。可以使用二进制-
(减法运算符)和-=
运算符(复合赋值)从委托组合中删除委托。可以比较委托的相等性(委托相等运算符)。
以下示例显示了许多委托的实例化及其相应的调用列表:
1 delegate void D(int x); 2 3 class C 4 { 5 public static void M1(int i) {...} 6 public static void M2(int i) {...} 7 8 } 9 10 class Test 11 { 12 static void Main() { 13 D cd1 = new D(C.M1); // M1 14 D cd2 = new D(C.M2); // M2 15 D cd3 = cd1 + cd2; // M1 + M2 16 D cd4 = cd3 + cd1; // M1 + M2 + M1 17 D cd5 = cd4 + cd3; // M1 + M2 + M1 + M1 + M2 18 } 19 20 }
当cd1
和cd2
实例化时,它们都封装了一个方法。在cd3
实例化时,它具有两个方法的调用列表,M1
并按M2
顺序。cd4
的调用列表包含M1
,, M2
和M1
,按顺序。最后cd5
的调用列表中包含M1
,M2
,M1
,M1
,并M2
按此顺序。有关组合(以及删除)委托的更多示例,请参阅委派调用。
委派兼容性
的方法或委托M
是兼容与委托类型D
如果以下所有条件都为真:
D
并且M
具有相同数量的参数,并且每个参数与相应的参数D
具有相同ref
或out
修饰符M
。- 对于每个值参数(带有no
ref
或out
modifier 的参数),从参数类型到相应的参数类型中存在标识转换(标识转换)或隐式引用转换(隐式引用转换)。D
M
- 对于每个
ref
或out
参数,参数类型in与参数类型D
相同M
。 - 从返回类型
M
到返回类型的存在标识或隐式引用转换D
。
委托实例化
委托的实例由delegate_creation_expression(委托创建表达式)或转换为委托类型创建。新创建的委托实例然后引用:
- delegate_creation_expression中引用的静态方法,或
null
在delegate_creation_expression中引用的目标对象(不可以)和实例方法,或- 另一位委托。
例如:
1 delegate void D(int x); 2 3 class C 4 { 5 public static void M1(int i) {...} 6 public void M2(int i) {...} 7 } 8 9 class Test 10 { 11 static void Main() { 12 D cd1 = new D(C.M1); // static method 13 C t = new C(); 14 D cd2 = new D(t.M2); // instance method 15 D cd3 = new D(cd2); // another delegate 16 } 17 }
实例化后,委托实例始终引用相同的目标对象和方法。请记住,当两个委托组合在一起,或者一个委托从另一个委托中删除时,新的委托会产生自己的调用列表; 组合或删除的委托的调用列表保持不变。
委托调用
C#提供了调用委托的特殊语法。当调用其调用列表包含一个条目的非null委托实例时,它将调用具有相同参数的one方法,并返回与引用方法相同的值。(有关委托调用的详细信息,请参阅委派调用。)如果在调用此类委托期间发生异常,并且该异常未在调用的方法中捕获,则在调用的方法中继续搜索异常catch子句委托,就好像该方法直接调用了该委托引用的方法一样。
通过按顺序同步调用调用列表中的每个方法来调用其调用列表包含多个条目的委托实例。所谓的每个方法都传递给委托实例的同一组参数。如果这样的委托调用包含引用参数(引用参数),则每个方法调用都将引用同一个变量; 通过调用列表中的一个方法对该变量的更改将对调用列表中的下一个方法可见。如果委托调用包括输出参数或返回值,则它们的最终值将来自列表中最后一个委托的调用。
如果在处理调用此类委托期间发生异常,并且该异常未在调用的方法中捕获,则在调用委托的方法中继续搜索异常catch子句,并在调用之后继续执行任何方法列表未被调用。
尝试调用值为null的委托实例会导致类型异常System.NullReferenceException
。
以下示例显示如何实例化,组合,删除和调用委托:
1 using System; 2 3 delegate void D(int x); 4 5 class C 6 { 7 public static void M1(int i) { 8 Console.WriteLine("C.M1: " + i); 9 } 10 11 public static void M2(int i) { 12 Console.WriteLine("C.M2: " + i); 13 } 14 15 public void M3(int i) { 16 Console.WriteLine("C.M3: " + i); 17 } 18 } 19 20 class Test 21 { 22 static void Main() { 23 D cd1 = new D(C.M1); 24 cd1(-1); // call M1 25 26 D cd2 = new D(C.M2); 27 cd2(-2); // call M2 28 29 D cd3 = cd1 + cd2; 30 cd3(10); // call M1 then M2 31 32 cd3 += cd1; 33 cd3(20); // call M1, M2, then M1 34 35 C c = new C(); 36 D cd4 = new D(c.M3); 37 cd3 += cd4; 38 cd3(30); // call M1, M2, M1, then M3 39 40 cd3 -= cd1; // remove last M1 41 cd3(40); // call M1, M2, then M3 42 43 cd3 -= cd4; 44 cd3(50); // call M1 then M2 45 46 cd3 -= cd2; 47 cd3(60); // call M1 48 49 cd3 -= cd2; // impossible removal is benign 50 cd3(60); // call M1 51 52 cd3 -= cd1; // invocation list is empty so cd3 is null 53 54 cd3(70); // System.NullReferenceException thrown 55 56 cd3 -= cd1; // impossible removal is benign 57 } 58 }
如语句所示cd3 += cd1;
,委托可以多次出现在调用列表中。在这种情况下,每次出现只需调用一次。在诸如此类的调用列表中,当删除该委托时,调用列表中的最后一个实例是实际删除的那个。
在执行最终语句之前,cd3 -= cd1;
委托cd3
引用空的调用列表。尝试从空列表中删除委托(或从非空列表中删除不存在的委托)不是错误。
产生的输出是:
1 C.M1: -1 2 C.M2: -2 3 C.M1: 10 4 C.M2: 10 5 C.M1: 20 6 C.M2: 20 7 C.M1: 20 8 C.M1: 30 9 C.M2: 30 10 C.M1: 30 11 C.M3: 30 12 C.M1: 40 13 C.M2: 40 14 C.M3: 40 15 C.M1: 50 16 C.M2: 50 17 C.M1: 60 18 C.M1: 60