C#委托和事件详解

委托

1.声明委托

使用委托和使用类一样,也需要经过定义和实例化两个步骤。首先必须定义要使用的委托,对于委托,定义它就是告诉编译器这种类型的委托表示哪种类型的方法。然后,必须创建该委托的一个或多个实例才能使用。编译器在后台将创建表示该委托的一个类。
定义委托的语法:

delegate void IntMethod(int x);
//定义了一个委托IntMethod,指定该委托的每个实例都可以包含一个或多个方法的引用,引用的方法必

须带有一个int参数,并返回void.
因为定义委托基本上是定义一个新类,所以可以在定义类的任何地方定义委托。也可以在委托的定义上使用修饰符:public,private,protected等。
委托派生自基类System.MulticastDelegate,MulticastDelegate又派生自基类System.Delegate.
类有两个不同的术语:“类”表示广义的定义,“对象”表示;类的实例。但委托只有一个术语。在创建委托的实例时,所创建的实例仍称为委托。必须从上下文中确定委托的具体含义。

2.使用委托

定义好委托之后,就可以创建它的一个实例,从而用它存储特定方法的细节。

delegate void IntMethod(int x);
 
        static void Fun(int x)
        {
            Console.WriteLine(x);
        }
 
        static void Main()
        {
          int x = 40;
          IntMethod intMethod = new IntMethod(Fun);
          intMethod(x);
          Console.ReadKey();
 
        }

委托在语法上总是接受一个参数的构造函数,这个参数就是委托引用的方法。这个方法必须匹配最初定义委托时的签名。
使用委托实例的名称,后面加上圆括号,如果需要参数就必须在圆括号内加上参数。
给委托实例提供圆括号和调用委托类的Invoke()方法完全相同:

intMethod(x);
intMethod.Invoke(x);

为了减少输入量,只需要给委托实例传递方法地址的名称就可以,这称为委托推断。

IntMethod intMethod = new IntMethod(Fun);
IntMethod intMethod =Fun;

委托推断可以在需要委托实例的任何地方使用。委托推断也可以用于事件,因为事件基于委托。(事件后面文章有介绍)
注意,使用委托可以调用任何类型对象的方法,不管是静态方法还是实例方法。

3.使用委托数组

//先在一个类中定义两个方法:
          class MathOperations
          {
            public static double MultiplyByTwo(double value)
            {
              return value * 2;
            }
 
            public static double Square(double value)
            {
              return value * value;
            }
          }
//定义一个返回double类型且带有double类型参数的委托
          delegate double DoubleOp(double x);
 
           
          class Program
          {
            static void Main()
            {
              //实例化委托数组,和实例化类的数组一样
              DoubleOp[] operations =
              {
                MathOperations.MultiplyByTwo,
                MathOperations.Square
              };
               
              //遍历数组,使用数组中的每个委托实例
              for (int i = 0; i < operations.Length; i++)
              {
                Console.WriteLine("Using operations[{0}]:", i);
                //将委托实例作为参数传递给ProcessAndDisplayNumber方法
                ProcessAndDisplayNumber(operations[i], 2.0);
                ProcessAndDisplayNumber(operations[i], 7.94);
                ProcessAndDisplayNumber(operations[i], 1.414);
                Console.WriteLine();
              }
            }
 
            static void ProcessAndDisplayNumber(DoubleOp action, double value)
            {
              //在ProcessAndDisplayNumber中调用委托,执行委托实例引用的方法
              double result = action(value);
              Console.WriteLine(
                 "Value is {0}, result of operation is {1}", value, result);
            }
          }

4.Action和Func委托

除了为每个参数和返回类型定义一个委托类型之外,还可以使用Action和Func委托。
泛型Action委托表示引用一个void返回类型的方法。这个委托类存在不同的变体,可以传递最多16种不同的参数类型。没有泛型参数的Action类调用没有参数的方法。Action调用带一个参数的方法,Action<in T1,in T2>调用带两个参数的方法,依次类推。
Func委托允许调用带返回类型的方法。与Action类似,Func也存在不同的变体,可以传递最多16种不同的参数类型和一个返回类型。Func委托类型可以调用无参数且带返回类型的方法。
下面使用Func委托实现一个不使用委托很难编写的一个功能:给对象数组排序,如果对象是int或string这样值类型的对象会容易排序,如果是要排序很多自定义的类型的对象,需要编写大量代码。使用委托会减少代码量。
定义包含比较方法的类:
BubbleSorter类实现了一个泛型方法 Sort,第一个参数是要排序的对象数组,第二个是一个委托,传递比较两个对象的方法。这样可以给Sort方法,传递自定义的比较方法。

class BubbleSorter
          {
            static public void Sort<T>(IList<T> sortArray, Func<T, T, bool> comparison)
            {
              bool swapped = true;
              do
              {
                swapped = false;
                for (int i = 0; i < sortArray.Count - 1; i++)
                {
                  //调用委托中引用的方法,比较两个对象
                  if (comparison(sortArray[i + 1], sortArray[i]))
                  {
                    T temp = sortArray[i];
                    sortArray[i] = sortArray[i + 1];
                    sortArray[i + 1] = temp;
                    swapped = true;
                  }
                }
              } while (swapped);
 
 
            }
          }

定义自定义的一个类

class Employee
          {
            public Employee(string name, decimal salary)
            {
              this.Name = name;
              this.Salary = salary;
            }
 
            public string Name { get; private set; }
            public decimal Salary { get; private set; }
 
            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;
            }
          }

客户端代码:

Employee[] employees =
          {
            new Employee("Bugs Bunny", 20000),
            new Employee("Elmer Fudd", 10000),
            new Employee("Daffy Duck", 25000),
            new Employee("Wile Coyote", 1000000.38m),
            new Employee("Foghorn Leghorn", 23000),
            new Employee("RoadRunner", 50000)
          };
            //Sort执行了自定义的Employee.CompareSalary方法
          BubbleSorter.Sort(employees, Employee.CompareSalary);
 
          foreach (var employee in employees)
          {
            Console.WriteLine(employee);
          }

5.多播委托

前面介绍的每个委托只包含一个方法的调用,委托也可以包含多个方法。这种委托称为多播委托。
如果调用多播委托,就可以按顺序调用多个方法,但如果委托的签名不是返回void,就只能得到委托调用的最后一个方法的结果。
使用+=添加方法,-=删除方法。

static void Main()
        {
          Action<double> operations = MathOperations.MultiplyByTwo;
          operations += MathOperations.Square;
 
          ProcessAndDisplayNumber(operations, 2.0);
          ProcessAndDisplayNumber(operations, 7.0);
          Console.WriteLine();
        }
 
        static void ProcessAndDisplayNumber(Action<double> action, double value)
        {
          Console.WriteLine();
          Console.WriteLine("ProcessAndDisplayNumber called with value = {0}", value);
          action(value);
 
        }
     
     
        class MathOperations
          {
            public static void MultiplyByTwo(double value)
            {
              double result = value * 2;
              Console.WriteLine("Multiplying by 2: {0} gives {1}", value, result);
            }
 
            public static void Square(double value)
            {
              double result = value * value;
              Console.WriteLine("Squaring: {0} gives {1}", value, result);
            }
          }

每次调用ProcessAndDisplayNumber方法,都会按顺序调用action委托实例中的两个方法。
输出:

ProcessAndDisplayNumber called with value = 2
Multiplying by 2: 2 gives 4
Squaring: 2 gives 4
 
ProcessAndDisplayNumber called with value = 7
Multiplying by 2: 7 gives 14
Squaring: 7 gives 49

委托还可以使用+,-运算符:

Action<double> operations1 = MathOperations.MultiplyByTwo;
Action<double> operations2 = MathOperations.Square;
Action<double> operations = operations1 + operations2;
operations = operations - operations2;

多播委托包含一个逐个调用的委托集合。如果其中一个方法抛出异常,整个迭代就会停止。

static void One()
{
  Console.WriteLine("One");
  throw new Exception("Error in one");
}
 
static void Two()
{
  Console.WriteLine("Two");
}
 
static void Main()
{
  Action d1 = One;
  d1 += Two;
 
  try
  {
    d1();
  }
  catch (Exception)
  {
    Console.WriteLine("Exception caught");
  }
}

委托只调用了第一个方法。因为第一个方法抛出异常,委托的迭代停止,不再调用Two()方法。
避免这个问题,可以使用Delegate类定义的GetInvocationList()方法,它返回一个Delegate对象数组:

Action d1 = One;
d1 += Two;
 
Delegate[] delegates = d1.GetInvocationList();
foreach (Action d in delegates)
{
  try
  {
    d();
  }
  catch (Exception)
  {
    Console.WriteLine("Exception caught");
  }
}

输出:

One
Exception caught
Two

使用GetInvocationList()方法可以为委托的每个方法传递不同的参数,获取每个方法的返回值。

static int One(int x)
{
  return x;
}
 
static int Two(int x)
{
  return x;
}
 
 
static void Main()
{
 
  Func<int,int> d1 = One;
  d1 += Two;
 
  Delegate[] delegates = d1.GetInvocationList();
  Func<int, int> d2 = (Func<int, int>)delegates[0];
  Console.WriteLine( d2(1));
 
  Func<int, int> d3 = (Func<int, int>)delegates[1];
  Console.WriteLine(d3(2));
 
  Console.ReadKey();
}

输出:

1
2
1
2

6.匿名方法

使用匿名方法可以将方法体直接赋给委托实例,而不需要定义一个方法。

static void Main()
{
  string mid = ", middle part,";
 
  Func<string, string> anonDel = delegate(string param)
  {
    param += mid;
    param += " and this was added to the string.";
    return param;
  };
  Console.WriteLine(anonDel("Start of string"));
 
}

上面代码不是把方法名赋给委托变量anonDel,而是一段代码,它前面是关键字delegate和参数列表。在使用匿名方法时,可以使用外部变量。
匿名方法的优点是减少了代码量。使用匿名方法,代码执行速度并没有加快。编译器仍定义了一个方法,该方法只有一个自动指定的名称。
使用匿名方法,必须遵守两条规则:

(1).在匿名方法中不能使用跳转语句(break,goto,continue)调到匿名方法的外部,外部的代码也不能调到匿名方法内部。
(2).匿名方法内部不能访问不安全的代码。也不能在匿名方法使用ref和out
如果需要匿名方法多次编写同一个功能时,就不要用匿名方法了。

事件

事件基于委托,可以为任何一种委托类型提供一种发布\订阅机制。
使用event关键字将一个委托类型定义为事件。

下面通过一个例子介绍事件:

//事件发布类
        public class PublishEvent
        {
            public delegate string Display(string str);
            public event Display DisplayEvent;
 
            //客户端代码通过调用这个方法触发事件
            public void Shows(string str)
            {
                if (DisplayEvent != null)
                {
                    DisplayEvent(str);
                }
            }
 
        }
 
        //事件侦听类,这个类订阅事件
        public class Listen1
        {
            public string MakeAlert(string str)
            {
                Console.WriteLine(str + "Listen1");
                return str + "Listen1";
            }
        }
        public class Listen2
        {
            public string ShowMsg(string str)
            {
                Console.WriteLine(str + "Listen2");
                return str + "Listen2";
            }
        }

客户端代码:

class Program
        {
            static void Main()
            {
                PublishEvent pe = new PublishEvent();
                Listen1 l1 =  new Listen1();
                Listen2 l2 = new Listen2();
 
                //变量l1和l2订阅了事件
                pe.DisplayEvent += l1.MakeAlert;
                pe.DisplayEvent += l2.ShowMsg;
 
                //触发事件
                pe.Shows("事件");
 
                Console.ReadKey();
 
            }
        }

事件就是一个特殊的委托,它是一个用于事件驱动模型的专用委托.你可以在客户代码中直接调用委托来激发委托指向的函数,而事件不可以,事件的触发只能由服务代码自己触发。也就是说在你的代码里委托你不但可以安排谁是它的调用函数,还可以直接调用它,而事件不能直接调用,只能通过某些操作触发。除此之此,事件拥有委托的所有功能,包括多播特性。即事件可以有多个事件处理函数,委托同样也可以是个多播委托.
事件是封装过的委托实例;委托是类型,事件是实例!
EventHandler.NET自带的委托,也用于定义事件。

  • 0
    点赞
  • 19
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

薪薪代码

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值