C#委托

来源:互联网 发布:淘宝投诉电话多少 编辑:程序博客网 时间:2024/05/02 04:24

在C和C++ 中,只能提取函数的地址,并作为一个参数传递它。C没有类型安全性。可以把任何函数传递给需要函数指针的方法。但是,这种直接方法不仅会导致一些关于类型安全性的问题,而且没有意识到:在进行面向对象编程时,几乎没有方法是孤立存在的,而是在调用方法前通常需要与类实例相关联。所以.NET FmmewOrk在语法上不允许使用这种直接方法。如果要传递方法,就必须把方法的细节封装在一种新类型的对象中,即委托。委托只是一种特殊类型的对象,其特殊之处在于,我们以前定义的所有对象都包含数据,而委托包含的只是一个或多个方法的地址。



1.声明委托

定义委托的语法如下:

delegate void MyMethodInvoker(int x);

在这个示例中,定义了一个委托MyMethodInvoker,并指定该委托的每个实例都可以包含一个方法的引用,该方法带有一个int参数,并返回void。理解委托的一个要点是它们的类型安全性非常高。在定义委托时,必须给出它所表示的方法的签名和返回类型等全部细节。

假设要定义一个返回值为string不带参数的委托,可以编写如下代码:

delegate string MyMethodInvoker();

根据定义的可见性,和委托的作用域,可以在委托的定义上应用任意常见的访问修饰符:publicprivate、protected等:
public delegate string MyMethodInvoker();


2.使用委托

下面的代码段说明了如何使用委托。

private delegate string GetAString();        static void Main()        {            int x = 40;            GetAString firststringMethod = new GetAString(x.ToString);            Console.WriteLine("string is (0)", firststringMethod());        }
在这段代码中,实例化了类型为GetAString的一个委托,并对它进行了初始化,使它引用整型变量x的ToString方法。。在C#中,委托在语法上总是接受一个参数的构造函数,这个参数就是委托引用的方法。这个方法必须匹配最初定义委托时的签名。

实际上,给委托实例提供圆括号与调用委托类的Invoke方法完全相同。因为firststringMethod是委托类型的一个变量,所以C#编译器会用firststringMethod.Invoke()代替firststringMethod()。

为了减少输入量,只要需要委托实例,就可以只传送地址的名称,这称为委托推断,只要编译器可以把委托实例解析为特定的类型,这个C#特性就是有效的。所以上面的委托初始化也可简写为:

GetAString firststringMethod=x.ToString;


3.Action<T>和Func<T>委托

除了为每个参数和返回类型定义一个新委托类型之外,还可以使用Action<T>和Func<T>委托。泛型Action<T>委托表示引用一个vod返回类型的方法。因为这个委托类存在不同的变体,所以可以传递至多16种不同的参数类型。没有泛型参数的Action类可调用没有参数的方法。Action<T>调用带一个参数的方法,Action<T1,T2>调用带两个参数的方法

Func<T>可以以类似的方式使用,Func<T>允许调用带返回类型的方法。。与Action<T>类似,Func<T>也定义了不同的变体,至多也可以传递16个参数类型和一个返回类型。Func<out TResult>委托类型可以调用带返回类型且无参数的方法,Func<T,out TResult>调用带一个参数的方法。

所以之前自定义的委托GetAString也可以用Func<T>代替。

Func<string> firststringMethod=x.ToString;


4.Bubblesorter示例

下面的示例将说明委托的真正用途。我们要编写一个类Bubblesorter,它实现一个静态法Sort(),这个方法的第一个参数是一个对象数组,把该数组按照升序重新排列

要实现对任何类型的对象数组排序,就必须要实现泛型方法Sort<T>(),另外该方法还需一个比较函数,其两个参数的类型是T,返回值为bool。因此,可以跟Sort<T>()指定如下签名:

static public void Sort<T>(IList<T> array,Func<T,T,bool> comparison);

下面定义Bubblesorter类:

class Bubblesorter    {        static public void Sort<T>(IList<T> array, Func<T, T, bool> comparison)        {            bool swapped = true;            do            {                swapped = false;                for (int i = 0; i < array.Count; i++)                {                    if (comparison(array[i + 1], array[i]))                    {                        swapped = true;                        T tmp = array[i + 1];                        array[i + 1] = array[i];                        array[i] = tmp;                    }                }            } while (swapped);        }    }
为了使用Bubblesorter类,下面再定义一个Employee类。

class Employee    {        public string Name { get; private set; }        public float Salary { get; private set; }        public Employee(string name, float salary)        {            Name = name;            Salary = salary;        }        public override string ToString()        {            return string.Format("{0},{1:C}",Name,Salary);        }        public static bool CompareSalary(Employee e1, Employee e2)        {            return e1.Salary < e2.Salary;        }    }

注意,为了匹配Func<T,T,bool>委托的签名,在这个类必须定义CompareSalary,它的参数是两个Employee引用,并返回一个布尔值。

下面编写客户端代码,完成程序。

static void Main()        {            Employee[] employee ={                                     new Employee("小王",4000),                                     new Employee("小赵",3200),                                     new Employee("小韩",3500),                                     new Employee("小宋",5000)                                };            Bubblesorter.Sort<Employee>(employee, Employee.CompareSalary);            foreach (Employee e in employee)            {                Console.WriteLine(e);            }        }

运行这段代码,结果如下:

5.多播委托

前面使用的每个委托都只包含一个方法调用。调用委托的次数与调用方法的次数相同。如果要调用多个方法,就需要多次显式调用这个委托。但是,委托也可以包含多个方法。这种委托称为多播委托。如果调用多播委托,就可以按顺序连续调用多个方法。为此,委托的签名就必须返回void,否则,就只能得到委托调用的最后一个方法的结果。

多播委托可以识别运算符+和+=,另外多播委托还识别-和-=,以从委托中删除方法调用。

为了说明多播委托的用法,下面定义一个MathOperation类,它可以做两个整型数的相加和想减。

class MathOperation    {        public static void Add(int a,int b)        {            Console.WriteLine("{0}+{1}={2}",a,b,a+b);        }        public static void Sub(int a, int b)        {            Console.WriteLine("{0}-{1}={2}", a, b, a - b);        }    }

下面测试多播委托

static void Main()        {            Action<int, int> action = MathOperation.Add;            action += MathOperation.Sub;            action(5, 3);            //从action中删除MathOperation.Sub            action -= MathOperation.Sub;            action(5, 3);        }

运行程序,结果如下:

如果正在使用多播委托,就应知道对同一个委托调用方法链的顺序并未正式定义。因此应避免编写依赖于以特定顺序调用方法的代码。

通过一个委托调用多个方法还可能导致一个大问题。多播委托包含一个逐个调用的委托集合。如果通过委托调用的其中一个方法抛出一个异常,整个迭代就会停止

为了说明这种情况,下面修改MathOperation类,在Add中抛出异常。

class MathOperation    {        public static void Add(int a,int b)        {            Console.WriteLine("{0}+{1}={2}",a,b,a+b);            throw new Exception();        }        public static void Sub(int a, int b)        {            Console.WriteLine("{0}-{1}={2}", a, b, a - b);        }    }

然后在主函数中也稍作修改

static void Main()        {            Action<int, int> action = MathOperation.Add;            action += MathOperation.Sub;            try            {                action(5, 3);            }            catch            {                Console.WriteLine("抛出异常……");            }        }

运行结果为:

委托只调用了Add方法。因为Add方法抛出了一个异常,所以委托的迭代会停止。

在这种情况下,为了避免这个问题,应自己迭代方法列表。Delegate类定义GetInvocationList方法,它返回一个Delegate对象数组。现在可以使用这个委托调用与委托直接相关的方法,捕获异常,并继续下一次迭代。

static void Main()        {            Action<int, int> action = MathOperation.Add;            action += MathOperation.Sub;            foreach (Action<int, int> d in action.GetInvocationList())            {                try                {                    d(5, 3);                }                catch                {                    Console.WriteLine("抛出异常……");                }            }                    }

修改了代码后运行应用程序,会看到在捕获了异常后,将继续迭代下一个方法。

6.匿名方法

到目前为止,要想使委托工作,方法必须己经存在(即委托是用它将调用的方法的相同签名定义的)。但还有另外一种使用委托的方式:即通过匿名方法。匿名方法是用作委托的参数的一段代码。

用匿名方法定义委托的语法与前面的定义并没有区别。但在实例化委托时,就有区别了。下面是一个非常简单的控制台应用程序,它说明了如何使用匿名方法:

static void Main()        {            Action<int, int> action = delegate(int a,int b)            {                Console.WriteLine("{0}+{1}={2}",a,b,a+b);            };            action(5, 3);        }






0 0
原创粉丝点击