分享

LilianChen C#:委托

 昵称10504424 2013-03-25

委托是一个类,它定义了方法的类型,使得可以将方法当作另一个方法的参数来进行传递,这种将方法动态地赋给参数的做法,可以避免在程序中大量使用If-Else(Switch)语句,同时使得程序具有更好的扩展性。

与使用一个类相似,在使用委托时,也需要经过两个步骤:

  • 定义要使用的委托。对于委托,定义它就是告诉编译器这种类型的委托代表了哪种类型的方法(在定义委托是,必须给出它所代表的方法签名和返回类型等全部细节。理解委托的一种好方式是把委托当作给方法签名和返回类型指定名称。)
delegate void IntMethodInoker(int x);
  • 创建委托的一个或多个实例
复制代码
 1 using System;
 2 
 3 namespace ConsoleApplication25
 4 {
 5     class Program
 6     {
 7         private delegate string GetAString();
 8         static void Main(string[] args)
 9         {
10             int x = 40;
11             GetAString firstStringMethod = new GetAString(x.ToString);
12             Console.WriteLine("String is {0}",firstStringMethod());
13             Console.ReadLine();
14         }
15     }
16 
17     
18 }
复制代码

提示:给定委托的实例可以表示任何类型的任何对象上的实例方法或静态方法--只要方法的签名匹配于委托的签名即可。
下面的示例,让firstStringMethod委托在另一个对象上调用其他两个方法,其中一个是实例方法,另一个是静态方法。

复制代码
 1 using System;
 2 
 3 namespace ConsoleApplication26
 4 {
 5     class Program
 6     {
 7         private delegate string GetAString();
 8         static void Main(string[] args)
 9         {
10             Currency balance = new Currency(34, 50);
11 
12             // firstStringMethod references an instance method
13             GetAString firstStringMethod = new GetAString(balance.ToString);
14             Console.WriteLine("String is {0}", firstStringMethod());
15 
16             // firstStringMethod references a static method
17             firstStringMethod = new GetAString(Currency.GetCurrencyUnit);
18             Console.WriteLine("String is {0}", firstStringMethod());
19 
20             Console.ReadLine();
21         }
22     }
23 
24     struct Currency
25     {
26         public uint Dollars;
27         public ushort Cents;
28         public Currency(uint dollars, ushort cents)
29         {
30             this.Dollars = dollars;
31             this.Cents = cents;
32         }
33 
34         public override string ToString()
35         {
36             return string.Format("${0}.{1,-2:00}", Dollars, Cents);
37         }
38 
39         public static string GetCurrencyUnit()
40         {
41             return "Dollar";
42         }
43 
44         public static explicit operator Currency(float value)
45         {
46             checked
47             {
48                 uint dollars = (uint)value;
49                 ushort cents = Convert.ToUInt16((value - dollars) * 100);
50                 return new Currency(dollars, cents);
51             }
52         }
53 
54         public static implicit operator float(Currency value)
55         {
56             return value.Dollars + (value.Cents / 100.0f);
57         }
58 
59         public static implicit operator Currency(uint value)
60         {
61             return new Currency(value, 0);
62         }
63 
64         public static implicit operator uint(Currency value)
65         {
66             return value.Dollars;
67         }
68     }
69 }
复制代码


示例2:

复制代码
 1 using System;
 2 
 3 namespace ConsoleApplication28
 4 {
 5     class Program
 6     {
 7         delegate double DoubleOp(double x);
 8         static void Main(string[] args)
 9         {
10             DoubleOp[] operations = { MathsOperation.MultiplyByTwo, MathsOperation.Square };
11             for (int i = 0; i < operations.Length; i++)
12             {
13                 Console.WriteLine("Using operations[{0}]:", i);
14                 ProcessAndDisplayNumber(operations[i], 3.0);
15             }
16             Console.ReadLine();
17         }
18 
19         static void ProcessAndDisplayNumber(DoubleOp action, double value)
20         {
21             double result = action(value);
22             Console.WriteLine("Value is {0}, result of the operation is {1}", value, result);
23         }
24     }
25 
26     class MathsOperation
27     {
28         public static double MultiplyByTwo(double value)
29         {
30             return value * 2;
31         }
32 
33         public static double Square(double value)
34         {
35             return value * value;
36         }
37     }
38 }
复制代码

示例2的关键一行是把委托传递给ProcessAndDisplayNumber()方法

ProcessAndDisplayNumber(operations[i], 3.0);

其中传递了委托名,但不带任何参数,假定operations[i]是一个委托,其语法是:

  • operations[i]表示“这个委托”。换言之,就是委托代表的方法
  • operations[i](2.0)表示“调用这个方法,参数放在括号中”。

示例3:

复制代码
 1 using System;
 2 
 3 namespace ConsoleApplication29
 4 {
 5     delegate bool Comparison(object x, object y);
 6     class Program
 7     {
 8         static void Main(string[] args)
 9         {
10             Employee[] employees = { new Employee("a", 20000), new Employee("b", 10000), new Employee("c", 15000) };
11             BubbleSorter.Sort(employees, new Comparison(Employee.CompareSalary));
12             foreach (var employee in employees)
13             {
14                 Console.WriteLine(employee);
15             }
16             Console.ReadLine();
17         }
18     }
19 
20     class BubbleSorter
21     {
22         static public void Sort(object[] sortArray, Comparison comparison)
23         {
24             for (int i = 0; i < sortArray.Length; i++)
25             {
26                 for (int j = i; j < sortArray.Length; j++)
27                 {
28                     if (comparison(sortArray[j], sortArray[i]))
29                     { 
30                         object temp=sortArray[i];
31                         sortArray[i]=sortArray[j];
32                         sortArray[j]=temp;
33                     }
34                 }
35             }
36         }
37     }
38 
39     class Employee
40     {
41         private string name;
42         private decimal salary;
43         public Employee(string name, decimal salary)
44         {
45             this.name = name;
46             this.salary = salary;
47         }
48 
49         public override string ToString()
50         {
51             return string.Format("{0},{1:C}", name, salary);
52         }
53 
54         public static bool CompareSalary(object x, object y)
55         {
56             Employee e1 = (Employee)x;
57             Employee e2 = (Employee)y;
58             return (e1.salary < e2.salary);
59         }
60     }
61 }
复制代码

输出:

 

多播委托:当一个委托只包含一个方法调用,调用委托的次数与调用方法的次数相同,如果调用多个方法,就需要多次显示调用这个委托。委托也可以包含多个方法,这种委托成为多播委托。

如果调用多播委托,就可以按顺序连续调用多个方法。为此,委托的签名就必须返回void,否则就只能得到委托调用的最后一个方法的结果。

复制代码
 1 using System;
 2 
 3 namespace ConsoleApplication30
 4 {
 5     delegate void DoubleOp(double value);
 6     class Program
 7     {
 8         static void Main(string[] args)
 9         {
10             DoubleOp operations = new DoubleOp(MathOperations.MultiplyByTwo);
11             operations += MathOperations.Square;
12             ProcessAndDisplayNumber(operations, 3.0);
13             Console.ReadLine();
14         }
15 
16         static void ProcessAndDisplayNumber(DoubleOp action, double valueToProcess)
17         {
18             Console.WriteLine();
19             Console.WriteLine("ProcessAndDisplayNumber called with value = {0}", valueToProcess);
20             action(valueToProcess);
21         }
22     }
23 
24     class MathOperations
25     {
26         public static void MultiplyByTwo(double value)
27         {
28             double result = value * 2;
29             Console.WriteLine("Multiplying by 2: {0} gives {1}", value, result);
30         }
31 
32         public static void Square(double value)
33         {
34             double result = value * value;
35             Console.WriteLine("Squaring: {0} gives {1}", value, result);
36         }
37     }
38 }
复制代码

注意:如果使用多播委托,就应注意对同一个委托调用方法链的顺序并未正式定义,一次应避免编写依赖于以特定顺序调用方法的代码。
通过一个委托调用多个方法还有一个问题,多播委托包含一个诸葛调用的委托集合,如果通过委托调用的一个方法抛出了异常,整个迭代就会停止。例如:

复制代码
 1 using System;
 2 
 3 namespace ConsoleApplication31
 4 {
 5     public delegate void DemoDelegate();
 6     class Program
 7     {
 8         static void Main(string[] args)
 9         {
10             DemoDelegate dl = new DemoDelegate(One);
11             dl += Two;
12             try
13             {
14                 dl();
15             }
16             catch(Exception)
17             {
18                 Console.WriteLine("Exception caught");
19             }
20             Console.ReadLine();
21         }
22 
23         static void One()
24         {
25             Console.WriteLine("One");
26             throw new Exception("Error in one");
27         }
28 
29         static void Two()
30         {
31             Console.WriteLine("Two");
32         }
33     }
34 }
复制代码

输出:
委托只调用了第一个方法,第一个方法抛出了异常,所以委托的迭代会停止,不再调用Two()方法。

为了避免这个问题,应手动迭代方法列表。Delegate类定义了方法GetInvocationList(),它返回一个Delegate对象数组。

复制代码
 1 using System;
 2 
 3 namespace ConsoleApplication31
 4 {
 5     public delegate void DemoDelegate();
 6     class Program
 7     {
 8         static void Main(string[] args)
 9         {
10             DemoDelegate dl = new DemoDelegate(One);
11             dl += new DemoDelegate(Two);
12             Delegate[] delegates = dl.GetInvocationList();
13             for (int i = 0; i < delegates.Length; i++)
14             {
15                 DemoDelegate d = (DemoDelegate)delegates[i];
16                 try
17                 {
18                     d();
19                 }
20                 catch (Exception)
21                 {
22                     Console.WriteLine("Exception caught");
23                 }
24             }
25             Console.ReadLine();
26         }
27 
28         static void One()
29         {
30             Console.WriteLine("One");
31             throw new Exception("Error in one");
32         }
33 
34         static void Two()
35         {
36             Console.WriteLine("Two");
37         }
38     }
39 }
复制代码

输出:

匿名方法:到目前为止,要想使委托工作,方法必须已经存在。但是使用委托还有另外一种方式:即通过匿名方法。匿名方法是用做委托参数的一个代码块。

用匿名方法定义委托的语法和前面的定义并没有区别,但是实例化委托时就有区别了。

复制代码
 1 using System;
 2 
 3 namespace ConsoleApplication32
 4 {
 5     class Program
 6     {
 7         delegate string DelegateTest(string val);
 8         static void Main(string[] args)
 9         {
10             string mid = ", middle part,";
11             DelegateTest anonDel = delegate(string param)
12             {
13                 param += mid;
14                 param += " and this was added to the string.";
15                 return param;
16             };
17 
18             Console.WriteLine(anonDel("Start of string"));
19             Console.ReadLine();
20         }
21     }
22 }
复制代码

在Main方法中,定义anonDel时,不是传送已知的方法名,而是使用一个简单的代码块。
在使用匿名方法是,必须遵循两个规则:在匿名方法中不能使用跳转语句调到该匿名方法的外部;反之亦然:匿名方法外部的跳转语句不能调到该匿名方法的内部。

  • 在匿名方法内部不能访问不完全的代码
  • 不能访问在匿名方法外部使用的ref和out参数,但可以使用在匿名方法外部定义的其他变量
  • 如果需要用匿名方法多次编写同一个功能,就不要使用匿名方法,而编写一个指定的方法比较好,因为该方法只能编写一次,以后可通过名称引用它

l表达式 : 为匿名方法提供了的一个新语法。

复制代码
 1 using System;
 2 
 3 namespace ConsoleApplication33
 4 {
 5     class Program
 6     {
 7         delegate string DelegateTest(string val);
 8         static void Main(string[] args)
 9         {
10             string mid = ", middle part,";
11             DelegateTest anonDel = param =>
12                 {
13                     param += mid;
14                     param += " and this was added to the string.";
15                     return param;
16                 };
17             Console.WriteLine(anonDel("Start of the string"));
18             Console.ReadLine();
19         }
20     }
21 }
复制代码

运算符=>的左边列出了匿名方法需要的参数。有几种编写方式:

1. 在括号中定义类型和变量名:

(string param) =>

2. 省去变量类型:

(param) =>

3. 如果只有一个参数,就可以删除括号:

DelegateTest anonDel = param =>

表达式的右边列出了实现代码。如果实现代码只有一行,也可以删除花括号和return语句。

利用l表达式 来写前面的多播委托示例,优点是它删除了类:

复制代码
 1 using System;
 2 
 3 namespace ConsoleApplication34
 4 {
 5     class Program
 6     {
 7         delegate double DoubleOp(double value);
 8         static void Main(string[] args)
 9         {
10             DoubleOp multByTwo = val => val * 2;
11             DoubleOp square = val => val * val;
12 
13             DoubleOp[] operations = { multByTwo, square };
14 
15             for (int i = 0; i < operations.Length; i++)
16             {
17                 Console.WriteLine("Using operations[{0}]", i);
18                 ProcessAndDiaplayNumber(operations[i], 3.0);
19             }
20 
21             Console.ReadLine();
22         }
23 
24         static void ProcessAndDiaplayNumber(DoubleOp action, double value)
25         {
26             double result= action(value);
27             Console.WriteLine("The result of the value: {0} is {1}", value, result);
28         }
29     }
30 }
复制代码


 返回类型协变:方法的返回类型可以派生于委托定义的类型。在下面的示例中,委托MyDelegate1定义为返回DelegateReturn类型。赋予委托实例d1的方法Method1返回DelegateReturn2类型,DelegateReturn2派生于DelegateReturn,因此满足了委托的需求,这成为返回类型协变。

复制代码
 1 namespace ConsoleApplication35
 2 {
 3     public class DelegateReturn
 4     {
 5     }
 6 
 7     public class DelegateReturn2 : DelegateReturn
 8     {
 9     }
10 
11     public delegate DelegateReturn MyDelegate1();
12 
13     class Program
14     {
15         static void Main()
16         {
17             MyDelegate1 d1 = Method1;
18             d1();
19         }
20 
21         static DelegateReturn2 Method1()
22         {
23             DelegateReturn2 d2 = new DelegateReturn2();
24             return d2;
25         }
26     }
27 }
复制代码

 

参数类型协变:表示委托定义的参数可能不同于委托调用的方法。这里是返回类型不同,因为方法使用的参数类型可能派生自委托定义的类型。

复制代码
 1 namespace ConsoleApplication36
 2 {
 3     public class DelegateParam
 4     { }
 5 
 6     public class DelegateParam2 : DelegateParam
 7     { }
 8 
 9     public delegate void MyDelegate2(DelegateParam2 p);
10     class Program
11     {
12         static void Main(string[] args)
13         {
14             MyDelegate2 d2 = Method2;
15             DelegateParam2 p = new DelegateParam2();
16             d2(p);
17         }
18 
19         static void Method2(DelegateParam p)
20         { }
21     }
22 }

    本站是提供个人知识管理的网络存储空间,所有内容均由用户发布,不代表本站观点。请注意甄别内容中的联系方式、诱导购买等信息,谨防诈骗。如发现有害或侵权内容,请点击一键举报。
    转藏 分享 献花(0

    0条评论

    发表

    请遵守用户 评论公约

    类似文章 更多