欢迎您来到“名字什么都是浮云”的博客空间!

C#高级编程9-第8章 委托、lamdba表达式和事件

委托、lamdba表达式和事件


1.引用方法

函数指针是一个指向内存位置的指针,不是类型安全的。无法判断实际指向。参数和返回类型也无从知晓。
.NET委托是类型安全的。定义了返回类型和参数类型,不仅包含方法引用,还可以包含多个方法引用。

2.委托

使用方法作为参数进行传递,必须把方法细节进行封装到一个新类型的对象中,即委托。
委托是一种特殊类型的对象。我们之前定义的对象都包含数据。而委托包含的是多个方法的地址。

声明委托

委托使用delegate声明。通过指定返回类型、签名以及参数类型进行创建。

创建委托的一个或多个实例,编译器将在后台创建表示该委托的一个类。

delegate void InitMethodInvoker(int x);

该委托方法无返回值,参数类型是int,每个实例都会有这个方法的引用。该委托类似于方法的定义,没有方法体。

委托可以使用访问修饰符进行修饰:

private delegate void InitMethodInvoker(int x);

定义委托后,创建它的实例,从而实现它的细节即方法体。

使用委托
class MathOperations
    {
        public static double MultiplayByTwo(double value)
        {
            return value * 2;
        }

        public static double Square(double value)
        {
            return value * value;
        }
    }
//这个类中定义了委托的实例
public delegate double DoubleOp(double x);//声明委托
static void Main(string[] args)
{
     DoubleOp[] operations = 
     {
         MathOperations.MultiplayByTwo,//指定委托实例方法
         MathOperations.Square
     };
     for (int i = 0; i < operations.Length; i++)
     {
      //将委托实例方法作为参数传递 ProcessAndDisplayNumber(operations[i],
2.0);//operations[i]是委托即方法参数 ProcessAndDisplayNumber(operations[i], 15); } } static void ProcessAndDisplayNumber(DoubleOp action,double value) { double result = action( value);//实现委托 Console.WriteLine("参数值value:" + value + ",结果值result:" + result); }

通常情况下需要做安全措施。如果这个方法使用DoubleOp委托实例方法作为参数传递,如果传入null值,到了action( value)会出现异常。因此需要在方法里面加上判断

ProcessAndDisplayNumber(null, 15);
static void ProcessAndDisplayNumber(DoubleOp action,double value)
{
  if(action!=null)
     double result = action( value);//实现委托
   Console.WriteLine("参数值value:" + value + ",结果值result:" + result);
}
系统委托

系统委托有4中:Action类、Func类、Predicate<T>、Comparison<T>委托

Action类的委托
  1. Action委托 封装一个方法,该方法不具有参数并且不返回值
  2. Action<T>委托 封装一个方法,该方法只有一个参数并且不返回值

  3. Action<T1,T2>委托 封装一个方法,该方法具有两个参数并且不返回值
  4. 复制代码
    static void Main(string[] args)
    {
      #region Action<T>委托示例
      //需求:打印出整型集合list的元素
      List<int> list = new List<int>() { 1, 2, 3, 4, 5 };
      //将匿名方法分配给 Action<T> 委托实例
      Action<int> concat1 = delegate(int i) { Console.WriteLine(i); };
      list.ForEach(concat1);
      //将 lambda 表达式分配给 Action<T> 委托实例
      Action<int> concat2 = (i => Console.WriteLine(i));
      list.ForEach(concat2);
      Console.ReadKey();
      #endregion 
    }
    复制代码

Func类的委托

  1. 1.Func(TResult)委托封装封装一个不具有参数但却返回 TResult 参数指定的类型值的方法
  2. Func(T,TResult)委托 封装一个具有一个参数并返回 TResult 参数指定的类型值的方法
  3. Func(T1,T2,TResult)委托 封装一个具有两个参数并返回 TResult 参数指定的类型值的方法
  4. 复制代码
    static void Main(string[] args)
    {
      #region Func<T,TResult>委托示例
      //需求:查找整型集合list中大于3的所有元素组成的新集合,并打印出集合元素
      List<int> list = new List<int>() { 1, 2, 3, 4, 5 };
      //将匿名方法分配给 Func<T,TResult> 委托实例
      Func<int, bool> concat1 = delegate(int i) { return i > 3; };
      var newlist1 = list.Where(concat1).ToList();
      //将 Lambda 表达式分配给 Func<T,TResult> 委托实例
      Func<int, bool> concat2 = i => i > 3;
      var newlist2 = list.Where(concat2).ToList();
      newlist1.ForEach(i => Console.WriteLine(i.ToString()));
      newlist2.ForEach(i => Console.WriteLine(i.ToString()));
      Console.ReadKey();
      #endregion
    }
    复制代码

Predicate<T>委托

    表示定义一组条件并确定指定对象是否符合这些条件的方法

  1. 复制代码
    static void Main(string[] args)
    {
      #region Predicate<T>委托示例
      //需求:查找整型集合list中大于3的所有元素组成的新集合,并打印出集合元素
      List<int> list = new List<int>() { 1, 2, 3, 4, 5 };
      //将匿名方法分配给 Predicate<T> 委托实例
      Predicate<int> concat1 = delegate(int i) { return i > 3; };
      var newlist1 = list.FindAll(concat1);
      //将 lambda 表达式分配给 Predicate<T> 委托实例
      Predicate<int> concat2 = (c => c > 3);
      var newlist2 = list.FindAll(concat2);
      newlist1.ForEach(i => Console.WriteLine(i));
      newlist2.ForEach(i => Console.WriteLine(i));
           Console.ReadKey();
      #endregion
    }
    复制代码

Comparison<T>委托

   表示比较同一类型的两个对象的方法

  • 复制代码
    static void Main(string[] args)
    {
      #region Comparison<T>委托示例
      //需求:将整型集合list中的所有元素倒序排列打印出来
      List<int> list = new List<int>() { 1, 2, 3, 4, 5 };
      //将匿名方法分配给 Comparison<T> 委托实例
      Comparison<int> concat1 = delegate(int i, int j) { return j - i; };
      //将 lambda 表达式分配给 Comparison<T> 委托实例
      Comparison<int> concat2 = (i, j) => j - i;
      list.Sort(concat1);
      list.ForEach(c => Console.WriteLine(c.ToString()));
      list.Sort(concat2);
      list.ForEach(c => Console.WriteLine(c.ToString()));
           Console.ReadKey();
      #endregion
    }
    复制代码
BubbleSorter

说明了委托真正的意图,首先定义一个Employee类,类中定义静态方法CompareSalary

public static bool CompareSalary(Employee e1, Employee e2)
{
      return e1.Salary < e2.Salary;
}

然后我们再定义一个类BubbleSorter,类中定义静态方法Sort

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

接下来我们分析一下:

Func<T, T, bool>是系统定义的委托,该委托具有2个参数,一个返回值,委托参数类型T根据调用Sort方法时进行指定(BubbleSorter.Sort<Employee>(..,..,))。然后我们看看所有执行代码:
static void Main()
        {
            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)
            };
//将Employee.CompareSalary方法作为参数进行传递,记住Employee.CompareSalary是一个委托实例类型,它目前不属于Func<T, T, bool>的实例,但是
//它符合Func<T, T, bool>类型,因此可以作为Func<T, T, bool>的实例进行参数传递。
      BubbleSorter.Sort(employees, Employee.CompareSalary);
      foreach (var employee in employees) 
      {
        Console.WriteLine(employee);
      }
}

 

多播委托

委托可以包含多个方法,可以多次显式调用这个委托。

需要注意的是,多播委托需要连续的调用多个方法,并且委托的返回结构是void,否则就只能得到最后一个方法的结果。

可以使用“+=”或者“-=”添加和删除方法。

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);
    }
  }
View Code
static void Main()
        {
            Action<double> operations = MathOperations.MultiplyByTwo;//   value*2
            operations += MathOperations.Square;//value*value

            ProcessAndDisplayNumber(operations, 2.0);
            ProcessAndDisplayNumber(operations, 7.94);
            ProcessAndDisplayNumber(operations, 1.414);
            Console.WriteLine();
        }

        static void ProcessAndDisplayNumber(Action<double> action, double value)
        {
            Console.WriteLine();
            Console.WriteLine("ProcessAndDisplayNumber called with value = {0}", value);
            action(value);

        }
View Code
ProcessAndDisplayNumber called with value = 2
Multiplying by 2: 2 gives 4
Squaring: 2 gives 4

ProcessAndDisplayNumber called with value = 7.94
Multiplying by 2: 7.94 gives 15.88
Squaring: 7.94 gives 63.0436

ProcessAndDisplayNumber called with value = 1.414
Multiplying by 2: 1.414 gives 2.828
Squaring: 1.414 gives 1.999396

虽然可以执行多个方法,但是如果执行到其中一个方法时失败了,那么后面的方法得不到执行。此时需要一个解决办法。

.NET中的系统委托定义了一个方法:GetInvocationList()通过这个方法能够得到所有需要执行的委托方法。然后我们迭代一下就可以处理其中一个方法失败,其他方法继续运行。

Action d1 = One;
d1 += Two;

Delegate[] delegates = d1.GetInvocationList();
foreach (Action d in delegates)
{
    try
    {
      d();
    }
    catch (Exception)
    {
       Console.WriteLine("Exception caught");
    }
}
static void One()
{
      Console.WriteLine("One");
      throw new Exception("Error in one");
}

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

 

 

匿名方法

原来使用委托,我们需要定义委托的委托的方法实例。现在我们需要简化它的操作,直接使用delegate关键字声明并定义。

在后面使用lamdba表达式之后会进行再次简化工作。使委托得到了更加灵活广泛的使用。

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

}

 

3.lamdba表达式

只要有委托的地方均可以使用lamdba表达式。声明委托的方法将变得更简单。

使用param =>代替了delegate(string param)的声明

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

结果:

start of string,middle part,and this was added to the string

 

参数

lamdba表达式多个参数进行定义声明

下面的委托(x, y)=>x*y是委托Func<double, double, double>的实例,

如果委托只有一个参数的话直接使用x=>y;就可以了;代表实现了Func<double,double>

如果委托有一个以上参数的话需要用括号包起来;=>指定了返回值。

如果需要在定义时指定参数的类型,也可以将参数的数据类型加上。

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

Func<double, double, double> twoParamsWithTypes = (double x, double y) => x * y;
Console.WriteLine(twoParamsWithTypes(4, 2));
多行代码

对于简单的(x, y) => x * y的表达式,编译器会给一条隐式的return返回语句。如果这个委托里面需要做的事情不仅仅是x*y这么简单呢,我们该如何定义?

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

通过大括号包起来做一些复杂的事情。

闭包

通过lambda表达式可以访问lambda表达式外部的变量,就是闭包。

someVal是lambda表达式外的变量,在表达式内部进行了访问。

int someVal = 5;
Func<int, int> f = x => x + someVal;

实际上对于表达式 x => x + someVal;编译器给它定义了一个匿名类;类中定义了有参构造方法,外部变量作为构造方法的参数传入到这个类中进行访问的。

public class AnonymousClass
    {
        private int someVal;
        public AnonymousClass(int someVal)//外部变量作为构造函数的参数。
        {
            this.someVal = someVal;
        }
        public int AnonymousMethod(int x)
        {
            return x + someVal;
        }
    }

 

foreach闭包

主要是说明闭包在C#4.0和C#5.0对这个的改变。

var values = new List<int>() { 10, 20, 30 };
var funcs = new List<Func<int>>();

foreach(var val in values)
{
    funcs.Add(() => val);
}

foreach(var f in funcs)
{
    Console.WriteLine(f);
} 

在C#4.0中会输出30,30,30而在C#5.0中会输出10,20,30

我们回到C#高级编程9的数组中看到之前有说明foreach的原始逻辑:

foreach (var p in persons)
{
   Console.WriteLine(p);
}
//通过IL中间语言生成后:
IEnumerator<Person> enumerator = persons.GetEnumerator();
while (enumerator)
{
    Person p = enumerator.Current;
   Console.WriteLine(p);
}

实际上在C#4.0中enumerator.Current是定义在循环外部的,每次迭代都使用这个值。循环结束该变量就是最后的值。

因此在C#4.0中需要将 funcs.Add(() => val);改为var v=val;funcs.Add(() => v);

4.事件

事件基于委托,为委托提供了发布和订阅机制。

事件发布程序和侦听器

首先我们看看这个信息:

交通工具: 巴士
乘客甲: 乘坐了 巴士
交通工具: 的士
乘客甲: 乘坐了 的士
乘客乙: 乘坐了 的士
交通工具: 地铁
乘客乙: 乘坐了 地铁

首先可以看出通过程序执行了“交通工具: 巴士”,然后执行了“乘客甲: 乘坐了 巴士”;

接下来看这块代码:

var dealer = new CarDealer();
var michael = new Consumer("乘客甲");

WeakEventManager<CarDealer, CarInfoEventArgs>.AddHandler(dealer, "NewCarInfo", michael.NewCarIsHere);
dealer.NewCar("巴士");
CarDealer和Consumer分别是2个对象,
WeakEventManager 提供基本类中使用的事件管理 弱事件模式。 该管理器添加和移除的事件 (或回调) 也使用该模式的侦听器。
下面是
AddHandler方法的定义:
public static void AddHandler(TEventSource source, string eventName, EventHandler<TEventArgs> handler);
source事件源;eventName是事件源对象里面的属性也就是事件名.handler是处理事件

执行的逻辑是这样的:

 WeakEventManager.AddHandler做的事情是:

  1)指定source对象中必须包含eventName属性,如果不存在该属性,会抛出异常。

  2)为eventName属性定义了实现handler;也就是说 Func<string, EventArgs> sourceEvent = eventName => handler;

 以这个为例:

WeakEventManager<CarDealer, CarInfoEventArgs>.AddHandler(dealer, "NewCarInfo", michael.NewCarIsHere);
dealer.NewCar("巴士");

   我们现在来想象一下CarDealer对象的定义:

 1)首先必须存在一个类,类中必须有一个类型为EventHandler<TEventArgs>的属性,属性名必须是NewCarInfo,需要记住的是该属性是一个事件。

 2)AddHandler方法为NewCarInfo属性指定了实现方法michael.NewCarIsHere,接下来就是进行调用NewCarInfo

   3)我们定义一个方法NewCar,使用NewCar来调用NewCarInfo,那么如何调用NewCarInfo呢?请看下EventHandler<TEventArgs>需要传入哪些参数

public delegate void EventHandler<TEventArgs>(object sender, TEventArgs e);

   4)由此我们得出了,再调用NewCarInfo方法时需要传入2个参数。sender是触发的事件对象,我们可以将dealer对象即NewCar方法中的this,还有一个就是TEventArgs e事件,这个事件也没有,这个事件是做什么用的呢,

  主要是提供事件的一些信息。接下来我们定义这个类为CarInfoEventArgs,当然这个类必须继承TEventArgs ,否则不能作为NewCarInfo方法参数。然后我们提供一个Car的字符串属性,用于记录事件信息。

 5)事件信息类也定义好了,我们需要调用NewCarInfo方法了,我们传入this还有new CarInfoEventArgs()对象。为了记录Car信息,我们将CarInfoEventArgs构造方法进行改造,传入Car参数。

   6)然后我们就有了调用:

if (NewCarInfo != null)
{
    NewCarInfo(this, new CarInfoEventArgs(car));
}

   7)实际上到此为止,核心逻辑都写完了,但是还漏了一步。我们一直在说:NewCarInfo方法的调用和实现,但是NewCarInfo具体实现还没有。

 8)我们再写一个类,类里面需要包含一个方法,方法必须符合EventHandler<TEventArgs>委托类型

public void NewCarIsHere(object sender, CarInfoEventArgs e)
{
      Console.WriteLine("{0}: 乘坐了 {1}", name, e.Car);
}

   9)我们已经有了sender和e的值即第6点中的: this和new CarInfoEventArgs(car)

 10)现在可以调用了。不好意思现在可以揭露代码的真相了.如果没有理解上面说的含义,没有关系,把下面的代码放到程序里面,调试一下吧。你就明白了。

static void Main(string[] args)
{
            var dealer = new CarDealer();

            var michael = new Consumer("乘客甲");
            WeakEventManager<CarDealer, CarInfoEventArgs>.AddHandler(dealer, "NewCarInfo", michael.NewCarIsHere);

            dealer.NewCar("巴士");
}
运行
public class CarInfoEventArgs : EventArgs
    {
        public CarInfoEventArgs(string car)
        {
            this.Car = car;
        }

        public string Car { get; private set; }
    }

    public class CarDealer
    {
        public event EventHandler<CarInfoEventArgs> NewCarInfo;
        public CarDealer()
        {

        }

        public void NewCar(string car)
        {
            Console.WriteLine("交通工具: {0}", car);
            if (NewCarInfo != null)
            {
                NewCarInfo(this, new CarInfoEventArgs(car));
            }
        }
    }
事件发布
public class Consumer 
  {
    private string name;

    public Consumer(string name)
    {
      this.name = name;
    }

    public void NewCarIsHere(object sender, CarInfoEventArgs e)
    {
      Console.WriteLine("{0}: 乘坐了 {1}", name, e.Car);
    }



  }
事件侦听

 现在结果出来了

交通工具: 巴士
乘客甲: 乘坐了 巴士

接下来修改一下运行代码:

static void Main(string[] args)
{
     var dealer = new CarDealer();

     var michael = new Consumer("乘客甲");
     WeakEventManager<CarDealer, CarInfoEventArgs>.AddHandler(dealer, "NewCarInfo", michael.NewCarIsHere);

     dealer.NewCar("巴士");

     var sebastian = new Consumer("乘客乙");
     WeakEventManager<CarDealer, CarInfoEventArgs>.AddHandler(dealer, "NewCarInfo", sebastian.NewCarIsHere);

     dealer.NewCar("的士");

     WeakEventManager<CarDealer, CarInfoEventArgs>.RemoveHandler(dealer, "NewCarInfo", michael.NewCarIsHere);

     dealer.NewCar("地铁");            
}

给NewCarInfo事件多添加了一个事件处理(事件的实现)sebastian.NewCarIsHere

事件还是原来的事件NewCarInfo,AddHandler()方法除了给NewCarInfo事件指定了事件处理,还可以为该事件添加多个事件处理。

RemoveHandler()方法用来移除所添加的事件处理。必须指定事件对象dealer,事件名,以及事件处理,如果提供的事件处理不存在该事件对象中,将不会移除。

弱事件

通过事件、直接连接到发布程序和侦听器。垃圾回收有问题,如:侦听器不再直接引用。发布程序仍有一个引用。垃圾回收器不能清空侦听器占用的内存。

这种强连接可以使用弱事件解决。使用WeakEventManager作为发布程序和侦听器的中介。我们已经理解了关于发布和侦听器,下面的就好理解了。

class Program
  {
    static void Main()
    {
      var dealer = new CarDealer();

      var michael = new Consumer("Michael");
      WeakCarInfoEventManager.AddListener(dealer, michael);

      dealer.NewCar("Mercedes");

      var sebastian = new Consumer("Sebastian");
      WeakCarInfoEventManager.AddListener(dealer, sebastian);

      dealer.NewCar("Ferrari");

      WeakCarInfoEventManager.RemoveListener(dealer, michael);

      dealer.NewCar("Red Bull Racing");
    }
  }
程序入口
public class WeakCarInfoEventManager : WeakEventManager
  {
    public static void AddListener(object source, IWeakEventListener listener)
    {
      CurrentManager.ProtectedAddListener(source, listener);
    }

    public static void RemoveListener(object source, IWeakEventListener listener)
    {
      CurrentManager.ProtectedRemoveListener(source, listener);
    }

    public static WeakCarInfoEventManager CurrentManager
    {
      get
      {
        WeakCarInfoEventManager manager = GetCurrentManager(typeof(WeakCarInfoEventManager)) as WeakCarInfoEventManager;
        if (manager == null)
        {
          manager = new WeakCarInfoEventManager();
          SetCurrentManager(typeof(WeakCarInfoEventManager), manager);
        }
        return manager;
      }
    }


    protected override void StartListening(object source)
    {
      (source as CarDealer).NewCarInfo += CarDealer_NewCarInfo;
    }

    void CarDealer_NewCarInfo(object sender, CarInfoEventArgs e)
    {
      DeliverEvent(sender, e);
    }
    protected override void StopListening(object source)
    {
      (source as CarDealer).NewCarInfo -= CarDealer_NewCarInfo;
    }
  }
弱事件类
public class Consumer : IWeakEventListener
  {
    private string name;

    public Consumer(string name)
    {
      this.name = name;
    }

    public void NewCarIsHere(object sender, CarInfoEventArgs e)
    {
      Console.WriteLine("{0}: car {1} is new", name, e.Car);
    }

    bool IWeakEventListener.ReceiveWeakEvent(Type managerType, object sender, EventArgs e)
    {
      NewCarIsHere(sender, e as CarInfoEventArgs);
      return true;
    }


  }
侦听器
public class CarInfoEventArgs : EventArgs
  {
    public CarInfoEventArgs(string car)
    {
      this.Car = car;
    }

    public string Car { get; private set; }
  }

  public class CarDealer
  {
    public event EventHandler<CarInfoEventArgs> NewCarInfo;

    public CarDealer()
    {

    }

    public void NewCar(string car)
    {
      Console.WriteLine("CarDealer, new car {0}", car);
      if (NewCarInfo != null)
      {
        NewCarInfo(this, new CarInfoEventArgs(car));
      }
    }
  }
发布程序

执行过程:

注册事件>添加事件>发布程序>事件侦听>执行事件
/*
 1)WeakCarInfoEventManager.AddListener
 2)WeakCarInfoEventManager.StartListening(object source)
 3)dealer.NewCar("Mercedes");
 4)NewCarInfo(this, new CarInfoEventArgs(car))
 5)WeakCarInfoEventManager.CarDealer_NewCarInfo(object sender, CarInfoEventArgs e)
 6)Consumer.ReceiveWeakEvent
 7)Consumer.NewCarIsHere
 8)WeakCarInfoEventManager.CarDealer_NewCarInfo执行完成
 */
运行过程

 

 

 

 

@author duanlaibao

@help C# Advanced programming.Nine

@date 13:31:36

 

posted @ 2017-05-11 13:32  名字什么都是浮云  阅读(584)  评论(0编辑  收藏  举报