C# 高级编程(笔记4)

第8章 委托、Lambda表达式和事件

1.多播委托:一个委托中包含多个方法

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

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

     static void One()
     {
         Console.WriteLine("One");
         throw new Exception("Error in one");
     }

     static void Two()
     {
         Console.WriteLine("Two");
     }

     Action d1 = One;
     d1 += Two;
     try
     {
        d1();
     }
     catch (Exception)
     {
        Console.WriteLine("Exception caught");
     }
  
     ----委托只调用了第一个方法。因为第一个方法抛出了一个异常,所以委托的迭代会停止,不再调Two()方法

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

     static void Main()
     {
         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

2.可以为每个参数以及返回类型自定义委托
   public delegate double deleOp(double s);

3.还可以使用Action<T>和Func<T>委托

   Action<T>委托:只带void返回类型
泛型Action<T>委托表示引用一个void返回类型的方法。这个委托类存在不同的变体,可以传递至多16种不同的参数类型。没有泛型参数的Action类可调用没有参数的方法。Action<in T>调用带一个参数的方法,Action<in T1, in T2>调用带两个参数的方法,Action<in T1, in T2,in T3, in T4,in T5, in T6,in T7, in T8>调用带8个参数的方法。

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

4.匿名方法

   还可以通过匿名方法来使用委托,

      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"));
      }

----Func <string, string>委托接受一个字符串参数,返回一个字符串。anonDel是这种委托类型的变量。不是把方法名赋予这个变量,而是使用一段简单的代码:它前面是关键字de1egate,后面是一个字符串参数;

----在使用匿名方法时,必须遵循两条规则。在匿名方法中不能使用跳转语句(break、goto或continue)跳到该匿名方法的外部,反之亦然:匿名方法外部的跳转语句不能跳到该匿名方法的内部。在匿名方法内部不能访问不安全的代码。另外,也不能访问在匿名方法外部使用的ref和out参数。但可以使用在匿名方法外部定义的其他变量。

----从c#3.0开始,可以使用Lambda表达式替代匿名方法

5.Lambda表达式

static void Main()
{
    string mid = ", middle part,";

    Func<string, string> lambda = param =>
    {
        param += mid;
        param += " and this was added to the string.";
        return param;
    };

    Console.WriteLine(lambda("Start of string"));
}

----Lmbda运算符"=>"的左边列出了需要的参数。Lambda运算符的右边定义了赋予委托变量lambda的方法的实现代码[即"=>"的右边为方法体]。

Lambda表达式有几种定义参数的方式:

A.如果只有一个参数,只写出参数名就足够了。下面的Lambda表达式使用了参数s。因为委托类型定义了一个string参数,所以s的类型就是string。实现代码调用String.Format()方法来返回一个字符串,

Func<string, string> oneParam = s => String.Format("change uppercase {0}", s.ToUpper());
Console.WriteLine(oneParam("test"));

B.如果委托使用多个参数,就把参数名放在括号中。这里参数x和y的类型是double,由Func<double,double,double>委托定义:

Func<double, double, double> twoParams = (x, y) => x * y;
Console.WriteLine(twoParams(3, 2));

上面也可以写成:Func
<double, double, double> twoParams = (x, y) => {return x * y;};

  ----如果Lambda表达式只有一条语句,在方法块内就不需要括号和return语句,因为编译器会添加一条隐式的return语句。

  ----但是,如果在Lambda表达式的实现代码中需要多条语句,就必须添加括号和return语句:

  ----为了方便,可以在括号中给变量名添加参数类型:

Func<double, double, double> twoParams = (double x, double y) => x * y;

6.事件

首先,事件是基于委托的,也可以说事件变量其实就是一个委托类型的变量,如

public event EventHandler<CarInfo> newCarEvent;//声明一个事件变量

//EventHandler是一个委托类型,其原型为:
public delegate void EventHandler<TEventArgs>(object sender, TEventArgs e)
                                             where TEventArgs : EVentArgs;//支持泛型委托
//泛型TEventArgs是关于事件的一些附加信息,就是事件发布者要告诉订阅者的信息

  好,既然是委托,那在要用到该事件的地方就得定义与委托原型一样的方法,这个就是事件订阅者要做的事情,拿客户买汽车的为例:

  //以下是两个订阅者
  //宝马汽车客户
    public class BMWBuyer
    {
        public void Buy(Object o, CarInfo e)
        {
            Console.WriteLine("{0}:{1}", "BMWBuyer", e.GetCarInfo());
        }
    }

  //大众汽车客户
    public class AutoDasBuyer
    {
        public void Buy(Object o, CarInfo e)
        {
            Console.WriteLine("{0}:{1}", "AutoDasBuyer", e.GetCarInfo());
        }
    }

----CarInfo类就是委托原型中的TEventArgs类型,也就是发布者要告诉订阅者的信息类,必须继承自EVentArgs类
  //事件附加信息CarInfo是一个类,必须继承自EventArgs,这个类中包含了本次事件的所有详细信息
    public class CarInfo : EventArgs
    {
        private String carName;
        public CarInfo(String name)
        {
            this.carName = name;
        }

        public String GetCarInfo()
        {
            return carName;
        }
    }

现在,事件订阅者有了,事件的详细信息也有了,还差一个事件发布者

    public class CarFactory
    {
        public event EventHandler<CarInfo> newCarEvent;
        public void StartEvent(String carName)
        {
            if (newCarEvent != null)
            {
                newCarEvent(this, new CarInfo(carName));//触发事件
            }
        }
    }

最后一步是把事件发布者与订阅者关联起来,通过“+=”,很简单

        static void Main(string[] args)
        {
            CarFactory factory = new CarFactory();

            //
            BMWBuyer bmwBuyer = new BMWBuyer();
            factory.newCarEvent += bmwBuyer.Buy;//关联发布者与订阅者
            factory.StartEvent("BMW");//触发事件

            //
            AutoDasBuyer autoBuyer = new AutoDasBuyer();
            factory.newCarEvent += autoBuyer.Buy;
            factory.StartEvent("AutoDaus");

            Console.ReadKey();
        }

//通过将发布者与订阅者关联上,发布者只要发布消息,所有的订阅者都可以收到这个消息

原文地址:https://www.cnblogs.com/notebook2011/p/2970333.html