委托与事件

为什么要有委托

  回调(callback)函数是Windows编程的一个重要部分。在C/C++的编程背景中,在许多的Windows API中使用过回调。回调函数实际是方法调用的指针,也称为函数指针。.NET一委托的形式实现了函数指针。他的特殊之处在于,.NET委托是类型安全的,C中的函数指针只不过是一个指向存储单元的指针,无法得到这个指针实际指向什么,像参数和返回值类型就更加无从知晓了。C#的委托是类型安全的方法引用。利用委托,你可以通过一个“方法对象”来调用一个方法链、创建变量来引用一个方法链以及将委托数据类型作为参数来传递方法。

委托的使用

1.定义委托

 委托是引用类型对象,他定义了方法的签名和返回值。与委托相关联的方法必须符合委托的签名。将方法赋值给委托后,委托调用时会调用该方法

[修饰符]delegate<返回值><委托名称>([参数列表])

修饰符和参数列表是可选的

例如:public delegate decimal Calculation(decimal val1,decimal val2)

2.创建委托的方法处理器

要使用委托,必须要有一个委托的方法处理器。该方法遵循委托的签名,包括返回值和参数 

例如:public decimal Add(decimal add1,decimal add2)

3.在委托上附加处理器

要调用委托的方法处理器,必须将其赋值给一个委托对象的变量

Calculation del=new Calculation(Add);//Calculation del=Add;

4.通过委托调用方法

可以通过委托来调用委托的方法处理器(当委托调用时也会调用它引用)

Console.WriteLine(del(5.35m,9.71m));

5.使用委托实现多播

多播委托是由两个或两个以上的委托拼凑而来。使用合并运算符+=将一个委托附加到另一个委托上可以创建多播委托。同样,可以使用移除运算符-=将单独的委托从多播委托中移除。

委托的内部机制

委托其实是一个类。他继承自System.MulticastDelegate,System.MulticastDelegate又继承自System.DelegateSystem.Delegate由一个对象引用和一个System.Reflection.MethodInfo类型的方法指针构成 

System.MulticastDelegate有三个重要的成员_methodPtr(用于获取委托所表示的方法),_target(用于获取当前调用的类实例),_invocationList(用于获取委托的调用列表)

委托的逆变与协变

Framework2.0之前,由于委托是类型安全的,他们不遵守继承的基础规则。委托不能绑定子类的方法。父类和子类视为两个类型。在Framework2.0之后,委托的协变的概念出现,这时委托可以按照继承的规则转换。委托的逆变指委托方法的参数可以按照继承的规则。

泛型委托

委托逆变如果都以object作为参数每次都要对参数的类型进行判断,很是麻烦。泛型委托既有逆变委托的特点又有泛型的特性即绑定不同类型的参数且不需要类型判断

例如:public delegate void MyGenericDelegate<T>(T arg);

 

static void Main(string[] args)
        {
            Console.WriteLine("*****Generic Delegates*****");
            MyGenericDelegate<string> strTarget = new MyGenericDelegate<string>(StringTarget);
            strTarget("Some string data");
            MyGenericDelegate<int> intTarget = new MyGenericDelegate<int>(IntTarget);
            intTarget(9);
            Console.ReadKey();

        }
        static void StringTarget(string arg)
        {
            Console.WriteLine("arg in uppercase is:{0}",arg.ToUpper());
        }
        static void IntTarget(int arg)
        {
            Console.WriteLine("++arg is:{0}", ++arg);
        }

 

显示结果

Action<T>泛型委托:封装一个方法,该方法可以传递至多16种不同参数类型没有返回值

例如:显示声明自定义委托

代码 

public class TestCustomDelegate
{
   public static void Main()
   {
      DisplayMessage messageTarget; 
      messageTarget = ShowWindowsMessage;
      messageTarget("Hello, World!");   
   }      
   private static void ShowWindowsMessage(string message)
   {
      MessageBox.Show(message);      
   }
}

Action<T>用法

代码

public class TestCustomDelegate
{
   public static void Main()
   {
      DisplayMessage messageTarget; 
      messageTarget = ShowWindowsMessage;
      messageTarget("Hello, World!");   
   }      
   private static void ShowWindowsMessage(string message)
   {
      MessageBox.Show(message);      
   }
}

Func<T, TResult> 委托:封装一个具有一个参数并返回 TResult 参数指定的类型值的方法

匿名方法 

当一个调用者想监听传进来的事件时,他必须定义一个唯一的与相关联委托签名的匹配方法,但是这样的方法很少被委托之外的任何程序调用。为了解决这个问题,现在可以在事件注册时直接将一个委托与一段代码相关联。这种代码的正式名称为匿名方法

static void Main(string[] args)
        {
            Console.WriteLine("*****Anonymous Methods*****");
            List<int> list = new List<int>();
            list.AddRange(new int[]{1,2,3,4,5,6});
            List<int> evenNumers = list.FindAll(delegate(int i)
                {
                    return i%2 == 0;
                });

            foreach (int i in evenNumers)
            {
                Console.WriteLine("{0: 0}",i); 
            }
            Console.Read();
        }

Lambda表达式

lambda表达式只是用更简单的方法来写匿名表达式

Lambda运算符“=>”的左边列出了需要的参数,Lambda运算符的右边定义了赋予lambda变量的方法的实现代码。

变量:如果只有一个参数,直接写出参数名就够了.

  如果有多个参数,就把参数放在花括号里

多行代码: 如果lambda表达式只有一条语句,就不需要大括号和return语句,编译器会添加一条隐式的return语句

如果lambda表达式实现代码有多条就必须添加大括号和return语句

static void Main(string[] args)
        {
            Console.WriteLine("*****Anonymous Methods*****");
            List<int> list = new List<int>();
            list.AddRange(new int[]{1,2,3,4,5,6});
            List<int> evenNumers = list.FindAll((int i)=>
                {
                    return i%2 == 0;
                });

            foreach (int i in evenNumers)
            {
                Console.WriteLine("{0: 0}",i); 
            }
            Console.Read();
        }

事件

委托存在的问题:

1.会有重复代码(定义委托,声明必要的成员变量,以及创建自定义的注册/注销方法来保护封装)

2.如果没有把委托对象设置为私有,调用者就可以直接访问委托对象。无法保证系统的封装性和安全性

C#提供event关键字,来自动提供注册和注销方法以及委托类型任何必要的成员变量。这些委托变量总是声明为私有的。

事件的使用

1.定义事件处理程序

[修饰符] event 类名 名称

由于所有的事件都基于委托,这里的类型必须是委托类型

2.注册事件

注册事件的程序就是订阅者。

将委托附加到事件上是通过事件连接运算符(+=)实现的。类似的,移除事件运算符(-=)用来从事件上分离订阅者。分离会阻止来自发布者对象后续的任何通知

3.实现事件

实现事件的方法和返回类型必须和事件的委托类型的签名和返回类型一致

4.触发事件

事件是从定义事件的类里触发的

事件的本质

事件其实是对委托的封装。event关键字会生成一个private的委托,使外界无法超越事件所在的对象而直接访问他们。并且会扩展两个隐藏的公共方法一个带add_前缀,一个带remove_前缀

发布/订阅模式

 

#region 01.定义一个类型用来保存所有需要发送给事件接受者的附加信息
    //01.定义一个类型用来保存所有需要发送给事件接受者的附加信息
    class CatInfoEventArgs : EventArgs
    {
        public CatInfoEventArgs(string car)
        {
            this.car = car;
        }
        public string car { get; private set; }
    } 
    #endregion
    #region 02.定义一个委托
    //02.定义一个委托
    delegate void EventHandler<TEventArgs>(object sender, TEventArgs e) where TEventArgs : EventArgs; 
    #endregion
    #region 03.定义一个事件源,这个类用来发布事件
    //03.定义一个事件源,这个类用来发布事件
    class CarDealer
    {
        public event EventHandler<CatInfoEventArgs> NewCarInfo;
        //事件处理程序
        public void NewCar(string car)
        {
            Console.WriteLine("CarDealer,New Car", car);
            if (NewCarInfo != null)
            {
                NewCarInfo(this, new CatInfoEventArgs(car));
            }
        }
    } 
    #endregion
    #region 04.定义一个事件监听器,这个类订阅CarDealer类的事件
    //04.定义一个事件监听器,这个类订阅CarDealer类的事件
    class Customer
    {
        string name;
        public Customer(string name)
        {
            this.name = name;
        }
        public void NewCarIsHere(object o, CatInfoEventArgs e)
        {
            Console.WriteLine("{0}:car {1} is new", name, e.car);
        }
    } 
    #endregion
    #region 05.连接发布程序和订阅器
    class Program
    {
        static void Main(string[] args)
        {
            //05.连接发布程序和订阅器
            var dealer = new CarDealer();
            var cjn = new Customer("cjn");
            //创建一个订阅
            dealer.NewCarInfo += cjn.NewCarIsHere;
            dealer.NewCar("Merdeces");

            var wan = new Customer("wan");
            dealer.NewCarInfo += wan.NewCarIsHere;
            dealer.NewCar("Toyato");

            Console.Read();
        }
    }
    #endregion

 

 

 

 

 

posted @ 2013-03-10 09:26  小跳蚤  阅读(242)  评论(0编辑  收藏  举报