C#综合揭秘——深入分析委托与事件

C#综合揭秘——深入分析委托与事件

本篇文章将为你介绍一下 Delegate 的使用方式,逐渐揭开 C# 当中事件(Event)的由来,它能使处理委托类型的过程变得更加简单。

还将为您解释委托的协变与逆变,以及如何使用 Delegate 使 Observer(观察者)模式的使用变得更加简单。

在事件的介绍上,会讲述事件的使用方式,并以ASP.NET的用户控件为例子,介绍一下自定义事件的使用。

最后一节,将介绍Predicate、Action、Func<T,TResult>多种泛型委托的使用和Lambda的发展过程与其使用方式。

一、委托类型的来由

记得在使用C语言的年代,整个项目中都充满着针指的身影,那时候流行使用函数指针来创建回调函数,使用回调可以把函数回调给程序中的另一个函数。但函数指针只是简单地把地址指向另一个函数,并不能传递其他额外信息。
在.NET中,在大部分时间里都没有指针的身影,因为指针被封闭在内部函数当中。可是回调函数却依然存在,它是以委托的方式来完成的。委托可以被视为一个更高级的指针,它不仅仅能把地址指向另一个函数,而且还能传递参数,返回值等多个信息。系统还为委托对象自动生成了同步、异步的调用方式,开发人员使用
BeginInvoke、EndInvoke 方法就可以抛开 Thread 而直接使用多线程调用 。


二、建立委托类

使用delegate就可以直接建立任何名称的委托类型,当进行系统编译时,系统就会自动生成此类型。您可以使用delegate void
MyDelegate() 方式建立一个委托类,并使用ILDASM.exe观察其成员。由ILDASM.exe
中可以看到,它继承了System.MulticastDelegate类,并自动生成BeginInvoke、EndInvoke、Invoke 等三个常用方法。

Invoke 方法是用于同步调用委托对象的对应方法,而BeginInvoke、EndInvoke是用于以异步方式调用对应方法的。
对于异步调用的使用方式,可以参考:C#综合揭秘——细说多线程

public class MyDelegate : MulticastDelegate
{
    //同步调用委托方法
    public virtual void Invoke();
    //异步调用委托方法
    public virtual IAsyncResult BeginInvoke(AsyncCallback callback, object state);
    public virtual void EndInvoke(IAsyncResult result);
}

MulticastDelegate是System.Delegate的子类,它是一个特殊类,编译器和其他工具可以从此类派生,但是自定义类不能显式地从此类进行派生。它支持多路广播委托,并拥有一个带有链接的委托列表,在调用多路广播委托时,系统将按照调用列表中的委托出现顺序来同步调用这些委托。

MulticastDelegate具有两个常用属性:Method、Target。其中Method 用于获取委托所表示的方法Target
用于获取当前调用的类实例。

MulticastDelegate有以下几个常用方法:

方法名称说明
Clone创建委托的浅表副本。
GetInvocationList按照调用顺序返回此多路广播委托的调用列表。
GetMethodImpl返回由当前的 MulticastDelegate 表示的静态方法。
GetObjectData用序列化该实例所需的所有数据填充 SerializationInfo 对象。
MemberwiseClone创建当前 Object 的浅表副本。
RemoveImpl调用列表中移除与指定委托相等的元素

MulticastDelegate与Delegate给委托对象建立了强大的支持,下面向各位详细介绍一下委托的使用方式。

三、委托使用方式

3.1 简单的委托

当建立委托对象时,委托的参数类型必须与委托方法相对应。只要向建立委托对象的构造函数中输入方法名称example.Method,委托就会直接绑定此方法。使用myDelegate.Invoke(string
message),就能显式调用委托方法。但在实际的操作中,我们无须用到 Invoke 方法,而只要直接使用myDelegate(string
message),就能调用委托方法。

class Program
{
    delegate void MyDelegate(string message);

    public class Example
    {
        public void Method(string message)
        {
            MessageBox.Show(message);
        }
    }

    static void Main(string[] args)
    {
        Example example = new Example();
        MyDelegate myDelegate = new MyDelegate(example.Method);
        myDelegate("Hello World");
        Console.ReadKey();
    }
}

3.2 带返回值的委托

当建立委托对象时,委托的返回值必须与委托方法相对应。使用下面的例子,方法将返回 “Hello Leslie” 。

class Program
{
    delegate string MyDelegate(string message);

    public class Example
    {
        public string Method(string name)
        {
            return "Hello " + name;
        }
    }

    static void Main(string[] args)
    {
        Example example = new Example();
        //绑定委托方法
        MyDelegate myDelegate = new MyDelegate(example.Method);
        //调用委托,获取返回值
        string message = myDelegate("Leslie");
        Console.WriteLine(message);
        Console.ReadKey();
    }
}

3.3 多路广播委托

在第二节前曾经提过,委托类继承于MulticastDelegate,这使委托对象支持多路广播,即委托对象可以绑定多个方法。当输入参数后,每个方法会按顺序进行迭代处理,并返回最后一个方法的计算结果。
下面的例子中,Price 类中有两个计算方法,Ordinary 按普通的9.5折计算,Favourable 按优惠价 8.5折计算。委托同时绑定了这两个方法,在输入参数100以后,Ordinary、Favourable这两个方法将按顺序迭代执行下去,最后返回 Favourable方法的计算结果 85。

public class Price
{
    public double Ordinary(double price)
    {
        double price1 = 0.95 * price;
        Console.WriteLine("Ordinary Price : " + price1);
        return price1;
    }

    public double Favourable(double price)
    {
        double price1 = 0.85 * price;
        Console.WriteLine("Favourable Price : " + price1);
        return price1;
    }
    static void Main(string[] args)
    {
        Price price = new Price();
        //绑定Ordinary方法
        MyDelegate myDelegate = new MyDelegate(price.Ordinary);
        //绑定Favourable方法
        myDelegate += new MyDelegate(price.Favourable);
        //调用委托
        Console.WriteLine("Current Price : " + myDelegate(100));
        Console.ReadKey();
    }
}

运行结果

3.4 浅谈Observer模式

回顾一下简单的 Observer 模式,它使用一对多的方式,可以让多个观察者同时关注同一个事物,并作出不同的响应。
例如下面的例子,Manager的底薪为基本工资的1.5倍,Assistant的底薪为基本工资的1.2倍。WageManager类的RegisterWorker方法与RemoveWorker方法可以用于注册和注销观察者,最后执行Execute方法可以对多个已注册的观察者同时输入参数。

 public class WageManager
{
    IList<Worker> workerList = new List<Worker>();

    public void RegisterWorker(Worker worker)
    {
        workerList.Add(worker);
    }

    public void RemoveWorker(Worker worker)
    {
        workerList.Remove(worker);
    }

    public void Excute(double basicWages)
    {
        if (workerList.Count != 0)
            foreach (var worker in workerList)
                worker.GetWages(basicWages);
    }

    static void Main(string[] args)
    {
        WageManager wageManager = new WageManager();
        //注册观察者  
        wageManager.RegisterWorker(new Manager());
        wageManager.RegisterWorker(new Assistant());
        //同时输入底薪3000元,分别进行计算  
        wageManager.Excute(3000);

        Console.ReadKey();
    }
}

public abstract class Worker
{
    public abstract double GetWages(double basicWages);
}

public class Manager : Worker
{
    //Manager实际工资为底薪1.5倍43
    public override double GetWages(double basicWages)
    {
        double totalWages = 1.5 * basicWages;
        Console.WriteLine("Manager's wages is " + totalWages);
        return totalWages;
    }
}

public class Assistant : Worker
{
    //Assistant实际工资为底薪的1.2倍54
    public override double GetWages(double basicWages)
    {
        double totalWages = 1.2 * basicWages;
        Console.WriteLine("Assistant's wages is " + totalWages);
        return totalWages;
    }
}

运行结果

开发 Observer
模式时借助委托,可以进一步简化开发的过程。由于委托对象支持多路广播,所以可以把Worker类省略。在WageManager类中建立了一个委托对象wageHandler,通过Attach与Detach方法可以分别加入或取消委托。如果观察者想对事物进行监测,只需要加入一个委托对象即可。记得在第二节曾经提过,委托的GetInvodationList方法能获取多路广播委托列表,在Execute方法中,就是通过去多路广播委托列表去判断所绑定的委托数量是否为0。

 public delegate double Handler(double basicWages);

public class Manager
{
    public double GetWages(double basicWages)
    {
        double totalWages = 1.5 * basicWages;
        Console.WriteLine("Manager's wages is : " + totalWages);
        return totalWages;
    }
}

public class Assistant
{
    public double GetWages(double basicWages)
    {
        double totalWages = 1.2 * basicWages;
        Console.WriteLine("Assistant's wages is : " + totalWages);
        return totalWages;
    }
}

public class WageManager
{
    private Handler wageHandler;

    //加入观察者
    public void Attach(Handler wageHandler1)
    {
        wageHandler += wageHandler1;
    }

    //删除观察者
    public void Detach(Handler wageHandler1)
    {
        wageHandler -= wageHandler1;
    }

    //通过GetInvodationList方法获取多路广播委托列表,如果观察者数量大于0即执行方法
    public void Execute(double basicWages)
    {
        if (wageHandler != null)
            if (wageHandler.GetInvocationList().Count() != 0)
                wageHandler(basicWages);
    }

    static void Main(string[] args)
    {
        WageManager wageManager = new WageManager();
        //加入Manager观察者
        Manager manager = new Manager();
        Handler managerHandler = new Handler(manager.GetWages);
        wageManager.Attach(managerHandler);

        //加入Assistant观察者
        Assistant assistant = new Assistant();
        Handler assistantHandler = new Handler(assistant.GetWages);
        wageManager.Attach(assistantHandler);

        //同时加入底薪3000元,分别进行计算
        wageManager.Execute(3000);
        Console.ReadKey();
    }
}

最后运行结果与上面的例子相同。

3.5 委托的协变与逆变

在 Framework 2.0 出现之前,委托协变这个概念还没有出现。此时因为委托是安全类型,它们不遵守继承的基础规则。即会这下面的情况:Manager
虽然是 Worker 的子类,但 GetWorkerHander 委托不能直接绑定 GetManager 方法,因为在委托当中它们的返回值 Manager
与 Worker 被视为完全无关的两个类型。

public class Worker
{ }
public class Manager : Worker
{ }

class Program
{
    public delegate Worker GetWorkerHandler(int id);
    public delegate Manager GetManagerHandler(int id);

    public static Worker GetWorker(int id)
    {
        Worker worker = new Worker();
        return worker;
    }

    public static Manager GetManager(int id)
    {
        Manager manager = new Manager();
        return manager;
    }

    static void Main(string[] args)
    {
        GetWorkerHandler workerHandler = new GetWorkerHandler(GetWorker);
        var worker = workerHandler(1);

        GetManagerHandler managerHandler = new GetManagerHandler(GetManager);
        var manager = managerHandler(2);
        Console.ReadKey();
    }
}

自从Framework 2.0 面试以后,委托协变的概念就应运而生,此时委托可以按照传统的继承规则进行转换。即 GetWorkerHandler 委托可以直接绑定 GetManager 方法。

public class Worker
{.......}
public class Manager : Worker
{.......}

class Program
{
    public delegate Worker GetWorkerHandler(int id);
    //在 Framework2.0 以上,委托 GetWorkerHandler 可绑定 GetWorker 与 GetManager 两个方法  

    public static Worker GetWorker(int id)
    {
        Worker worker = new Worker();
        return worker;
    }

    public static Manager GetManager(int id)
    {
        Manager manager = new Manager();
        return manager;
    }

    static void Main(string[] args)
    {
        GetWorkerHandler workerHandler = new GetWorkerHandler(GetWorker);
        Worker worker = workerHandler(1);
        GetWorkerHandler managerHandler = new GetWorkerHandler(GetManager);
        Manager manager = managerHandler(2) as Manager;
        Console.ReadKey();
    }
}

委托逆变,是指委托方法的参数同样可以接收 “继承” 这个传统规则。像下面的例子,以 object 为参数的委托,可以接受任何 object 子类的对象作为参数。最后可以在处理方法中使用 is 对输入数据的类型进行判断,分别处理对不同的类型的对象。

class Program
{  
    public delegate void Handler(object obj);  
   
    public static void GetMessage(object message)
    {  
        if (message is string)  
            Console.WriteLine("His name is : " + message.ToString());  
        if (message is int)  
            Console.WriteLine("His age is : " + message.ToString());  
    }

    static void Main(string[] args)
    {
        Handler handler = new Handler(GetMessage);
        handler(29);
        Console.ReadKey();
    }
}

运行结果

注意: 委托与其绑定方法的参数必须一至,即当 Handler 所输入的参数为 A 类型,其绑定方法 GetMessage 的参数也必须为 A类或者 A 的父类 。相反,当绑定方法的参数为 A 的子类,系统也无法辨认。

3.6 泛型委托

委托逆变虽然实用,但如果都以 object 作为参数,则需要每次都对参数进行类型的判断,这不禁令人感到厌烦。
为此,泛型委托应运而生,泛型委托有着委托逆变的优点,同时利用泛型的特性,可以使一个委托绑定多个不同类型参数的方法,而且在方法中不需要使用 is
进行类型判断,从而简化了代码。

class Program
{
    public delegate void Handler<T>(T obj);

    public static void GetWorkerWages(Worker worker)
    {
        Console.WriteLine("Worker's total wages is " + worker.Wages);
    }

    public static void GetManagerWages(Manager manager)
    {
        Console.WriteLine("Manager's total wages is " + manager.Wages);
    }

    static void Main(string[] args)
    {
        Handler<Worker> workerHander = new Handler<Worker>(GetWorkerWages);
        Worker worker = new Worker();
        worker.Wages = 3000;
        workerHander(worker);

        Handler<Manager> managerHandler = new Handler<Manager>(GetManagerWages);
        Manager manager = new Manager();
        manager.Wages = 4500;
        managerHandler(manager);

        Console.ReadKey();
    }
}

运行结果

四、深入解析事件

4.1 事件的由来

在介绍事件之前大家可以先看看下面的例子, PriceManager 负责对商品价格进行处理,当委托对象 GetPriceHandler
的返回值大于100元,按8.8折计算,低于100元按原价计算。

public delegate double PriceHandler();

public class PriceManager
{
    public PriceHandler GetPriceHandler;

    //委托处理,当价格高于100元按8.8折计算,其他按原价计算 
    public double GetPrice()
    {
        if (GetPriceHandler.GetInvocationList().Count() > 0)
        {
            if (GetPriceHandler() > 100)
                return GetPriceHandler() * 0.88;
            else
                return GetPriceHandler();
        }
        return -1;
    }
}

class Program
{
    static void Main(string[] args)
    {
        PriceManager priceManager = new PriceManager();

        //调用priceManager的GetPrice方法获取价格  
        //直接调用委托的Invoke获取价格,两者进行比较
        priceManager.GetPriceHandler = new PriceHandler(ComputerPrice);
        Console.WriteLine(string.Format("GetPrice\n  Computer's price is {0}!",
            priceManager.GetPrice()));
        Console.WriteLine(string.Format("Invoke\n  Computer's price is {0}!",
            priceManager.GetPriceHandler.Invoke()));

        Console.WriteLine();

        priceManager.GetPriceHandler = new PriceHandler(BookPrice);
        Console.WriteLine(string.Format("GetPrice\n  Book's price is {0}!",
            priceManager.GetPrice()));
        Console.WriteLine(string.Format("Invoke\n  Book's price is {0}!",
            priceManager.GetPriceHandler.Invoke()));

        Console.ReadKey();
    }
    //书本价格为98元
    public static double BookPrice()
    {
        return 98.0;
    }
    //计算机价格为8800元
    public static double ComputerPrice()
    {
        return 8800.0;
    }
}

运行结果

观察运行的结果,如果把委托对象 GetPriceHandler 设置为 public ,外界可以直接调用 GetPriceHandler.Invoke获取运行结果而移除了 GetPrice 方法的处理,这正是开发人员最不想看到的。 为了保证系统的封装性,开发往往需要把委托对象 GetPriceHandler 设置为 private, 再分别加入AddHandler,RemoveHandler 方法对 GetPriceHandler 委托对象进行封装。

public delegate double PriceHandler();  
   
public class PriceManager
{  
    private PriceHandler GetPriceHandler;  
   
    //委托处理,当价格高于100元按8.8折计算,其他按原价计算
    public double GetPrice()  
    {  
        if (GetPriceHandler!=null)  
        {  
            if (GetPriceHandler() > 100)  
                return GetPriceHandler()*0.88;  
            else  
                return GetPriceHandler();  
        }
        return -1;
    }  

    public void AddHandler(PriceHandler handler)
    {
        GetPriceHandler += handler;
    }

    public void RemoveHandler(PriceHandler handler)
    {
        GetPriceHandler -= handler;
    }
}  

为了保存封装性,很多操作都需要加入AddHandler、RemoveHandler 这些相似的方法代码,这未免令人感到厌烦。
为了进一步简化操作,事件这个概念应运而生。

4.2 事件的定义

事件(event)可被视作为一种特别的委托,它为委托对象隐式地建立起add_XXX、remove_XXX
两个方法,用作注册与注销事件的处理方法。而且事件对应的变量成员将会被视为 private
变量,外界无法超越事件所在对象直接访问它们,这使事件具备良好的封装性,而且免除了add_XXX、remove_XXX等繁琐的代码。

public class EventTest
{
    public delegate void MyDelegate();
    public event MyDelegate MyEvent;
}

观察事件的编译过程可知,在编译的时候,系统为 MyEvent 事件自动建立add_MyEvent、remove_MyEvent 方法。

4.3 事件的使用方式

事件能通过+=和-=两个方式注册或者注销对其处理的方法,使用+=与-=操作符的时候,系统会自动调用对应的 add_XXX、remove_XXX 进行处理。
值得留意,在PersonManager类的Execute方法中,如果 MyEvent
绑定的处理方法不为空,即可使用MyEvent(string)引发事件。但如果在外界的 main 方法中直接使用 personManager.MyEvent
(string) 来引发事件,系统将引发错误报告。这正是因为事件具备了良好的封装性,使外界不能超越事件所在的对象访问其变量成员。

注意在事件所处的对象之外,事件只能出现在+=,-=的左方。

此时,开发人员无须手动添加 add_XXX、remove_XXX 的方法,就可实现与4.1例子中的相同功能,实现了良好的封装。

public delegate void MyDelegate(string name);  
   
public class PersonManager
{  
    public event MyDelegate MyEvent;  
   
    //执行事件 
    public void Execute(string name)  
    {  
        if (MyEvent != null)  
            MyEvent(name);  
    }
}  

class Program
{
    static void Main(string[] args)
    {
        PersonManager personManager = new PersonManager();
        //绑定事件处理方法
        personManager.MyEvent += new MyDelegate(GetName);  
        personManager.Execute("Leslie");
        Console.ReadKey();
    }

    public static void GetName(string name)
    {
        Console.WriteLine("My name is " + name);
    }
}

4.4 事件处理方法的绑定

在绑定事件处理方法的时候,事件出现在+=、-= 操作符的左边,对应的委托对象出现在+=、-=
操作符的右边。对应以上例子,事件提供了更简单的绑定方式,只需要在+=、-= 操作符的右方写上方法名称,系统就能自动辩认。

public delegate void MyDelegate(string name);

public class PersonManager
{
    public event MyDelegate MyEvent;  
}

class Program
{
    static void Main(string[] args)
    {
        PersonManager personManager = new PersonManager();
        //绑定事件处理方法15
        personManager.MyEvent += GetName;

    }

    public static void GetName(string name)
    { }
}

如果觉得编写 GetName 方法过于麻烦,你还可以使用匿名方法绑定事件的处理。

public delegate void MyDelegate(string name);

public class PersonManager
{
    public event MyDelegate MyEvent;

    //执行事件 
    public void Execute(string name)
    {
        if (MyEvent != null)
            MyEvent(name);
    }

    static void Main(string[] args)
    {
        PersonManager personManager = new PersonManager();
        //使用匿名方法绑定事件的处理
        personManager.MyEvent += delegate (string name)
        {
            Console.WriteLine("My name is " + name);
        };
        personManager.Execute("Leslie");
        Console.ReadKey();
    }
}

4.5 C#控件中的事件

在C#控件中存在多个的事件,像Click、TextChanged、SelectIndexChanged 等等,很多都是通过 EventHandler委托绑定事件的处理方法的,EventHandler 可说是C#控件中最常见的委托 。

public delegate void EventHandler (Object sender, EventArgs e)

EventHandler 委托并无返回值,sender 代表引发事件的控件对象,e 代表由该事件生成的数据。

在ASP.NET中可以直接通过btn.Click+=new EventHandler(btn_onclick) 的方式为控件绑定处理方法。

<html xmlns="http://www.w3.org/1999/xhtml">  
<head runat="server">  
    <title></title>  
    <script type="text/C#" runat="server">        
        protected void Page_Load(object sender, EventArgs e)  
        {  
            btn.Click += new EventHandler(btn_onclick);  
        }  
        public void btn_onclick(object obj, EventArgs e)  
        {  
            Button btn = (Button)obj;  
            Response.Write(btn.Text);  
        }  
    </script>  
</head>  
<body>  
    <form id="form1" runat="server">  
    <div>  
    <asp:Button ID="btn" runat="server" Text="Button"/>  
    </div>  
    </form>  
</body>  
</html>

更多时候,只需要在页面使用 OnClick=“btn_onclick" 方法,在编译的时候系统就会自动对事件处理方法进行绑定。

<html xmlns="http://www.w3.org/1999/xhtml">  
    <head runat="server">  
        <title></title>  
        <script type="text/C#" runat="server">          
            public void btn_onclick(object obj, EventArgs e)  
            {  
                Button btn = (Button)obj;  
                Response.Write(btn.Text);  
            }  
    </script>  
</head>  
<body>  
    <form id="form1" runat="server">  
    <div>  
        <asp:Button ID="btn" runat="server" Text="Button" OnClick="btn_onclick"/>  
    </div>  
    </form>  
</body>  
</html>

EventHandler 只是 EventHandler 泛型委托的一个简单例子。事实上,大家可以利用 EventHandler 构造出所需要的委托。

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

在EventHandler中,sender代表事件源,e 代表派生自EventArgs类的事件参数。开发人员可以建立派生自EventArgs的类,从中加入需要使用到的事件参数,然后建立 EventHandler 委托。

下面的例子中,先建立一个派生自EventArgs的类MyEventArgs作为事件参数,然后在EventManager中建立事件myEvent , 通过 Execute 方法可以激发事件。最后在测试中绑定 myEvent 的处理方法 ShowMessage,在ShowMessage显示myEventArgs 的事件参数 Message。

public class MyEventArgs : EventArgs  
{  
    private string args;  
   
    public MyEventArgs(string message)
    {  
        args = message;  
    }  
   
    public string Message
    {  
        get { return args; }
        set { args = value; }  
    }  
}  

public class EventManager
{
    public event EventHandler<MyEventArgs> myEvent;

    public void Execute(string message)
    {
        if (myEvent != null)
            myEvent(this, new MyEventArgs(message));
    }
}  

class Program
{
    static void Main(string[] args)
    {
        EventManager eventManager = new EventManager();
        eventManager.myEvent += new EventHandler<MyEventArgs>(ShowMessage);
        eventManager.Execute("How are you!");
        Console.ReadKey();
    }

    public static void ShowMessage(object obj, MyEventArgs e)
    {
        Console.WriteLine(e.Message);
    }
}

运行结果

4.6 为用户控件建立事件

在ASP.NET开发中,页面往往会出现很多类似的控件与代码,开发人员可以通过用户控件来避免重复的代码。但往往同一个用户控件,在不同的页面中需要有不同的响应。此时为用户控件建立事件,便可轻松地解决此问题。
下面例子中,在用户控件 MyControl 中建立存在一个GridView控件,GridView 控件通过 GetPersonList方法获取数据源。在用户控件中还定义了 RowCommand 事件,在 GridView 的 GridView_RowCommand方法中激发此事件。这样,在页面使用此控件时,开发人员就可以定义不同的方法处理 RowCommand 事件。

1 public class Person
 {  
     public int ID
     { get; set; }
     public string Name
     { get; set; }
     public int Age
     { get; set; }
 }  

用户控件:

 <!--用户控件-->
 <%@ Control Language = "C#" AutoEventWireup="true" CodeFile="MyControl.ascx.cs" Inherits="MyControl" %>  
 <script type="text/C#" runat="server">    
    protected void Page_Load(object sender, EventArgs e)
    {
        GridView1.DataSource = GetPersonList();
        GridView1.DataBind();
    }
    //绑定数据源
    protected IList<Person> GetPersonList()  
    {
        IList<Person> list = new List<Person>();
        Person person1 = new Person();
        person1.ID = 1;
        person1.Name = "Leslie";
        person1.Age = 29;
        list.Add(person1);
        ...........  
        return list;
    }
    public event GridViewCommandEventHandler RowCommand;
    protected void GridView1_RowCommand(object sender, GridViewCommandEventArgs e)
    {
        if (RowCommand != null)
            RowCommand(sender, e);
    }
 </script>
 <div>
 <asp:GridView ID = "GridView1" runat="server" AutoGenerateColumns="False"   
         onrowcommand="GridView1_RowCommand">  
       <Columns>  
           <asp:BoundField DataField = "ID" HeaderText="ID"/>  
           <asp:BoundField DataField = "Name" HeaderText="Name"/>  
           <asp:BoundField DataField = "Age" HeaderText="Age"/>  
           <asp:ButtonField CommandName = "Get" Text="Select"/>  
       </Columns>  
    </asp:GridView>
 </div>

页面代码:

 <!--页面代码-->
 <%@ Page Language = "C#" AutoEventWireup="true" CodeFile="Default2.aspx.cs" Inherits="Default2" %>  
 <%@ Register Src="~/MyControl.ascx" TagPrefix="ascx" TagName="myControl" %>  
 <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">  
   
 <html xmlns="http://www.w3.org/1999/xhtml">  
 <head runat="server">  
     <title></title>  
     <script type="text/C#" runat="server">     
        protected void myControl_RowCommand(object sender, GridViewCommandEventArgs e)
        {
           if (e.CommandName == "Get")
           {
             GridView gridView = (GridView)sender;
             int index = int.Parse(e.CommandArgument.ToString());
             label.Text = gridView.Rows[index].Cells[1].Text;
           }
        }
 </script>
 </head>
 <body>
 <form id = "form1" runat = "server">
 <div>
 <ascx:myControl ID = "myControl" runat="server" OnRowCommand="myControl_RowCommand"></ascx:myControl>
 <br/>
         Select Name : <asp:Label ID = "label" runat="server"></asp:Label>< br/>
 </div>
 </form>
 </body>
 </html>

运行结果:

使用控件已有的事件固然简单,但它限制了传送的参数类型,使开发人员无法传送额外的自定义参数。在结构比较复杂的用户控件中,使用已有的控件事件,显然不够方便,此时,您可以考虑为用户控件建立自定义事件。
首先用户控件中包含订单信息与订单明细列表,首先定义一个事件参数 MyEventArgs,里面包含了订单信息与一个 OrderItem
数组。然后建立用户控件的委托MyDelegate 与对应的事件 MyEvent,在 Button 的 Click 事件中激发 MyEvent
自定义事件。这样在页面处理方法 myControl_Click 中就可以通过事件参数 MyEventArgs 获取用户控件中的属性,计算订单的总体价格。

<!--   基础类    -->  
public class OrderItem  
{  
    public OrderItem(string id,string goods,double price,int count)  
    {  
        this.OrderItemID = id;     //明细单ID  
        this.Goods = goods;        //商品名称  
        this.Price = price;        //商品单价  
        this.Count = count;        //商品数量   
    }  
    
    public string OrderItemID  
    { get; set; }  
    public string Goods  
    { get; set; }  
    public double Price  
    { get; set; }  
    public int Count  
    { get; set; }  
}  
    
/// 事件参数  
public class MyEventArgs:EventArgs  
{  
    public MyEventArgs(string name,string address,string tel,  string orderCode,IList<OrderItem> orderItemList)  
    {  
        Name = name;    //买家姓名  
        Address = address;    //买家地址  
        Tel = tel;    //买家电话  
        OrderCode = orderCode;     //订单号码  
        OrderItemList = orderItemList;     //订单明细  
    }  
    
    public string Name  
    { get;set; }  
    public string Address  
    { get; set; }  
    public string Tel  
    { get; set; }  
    public string OrderCode  
    { get; set; }  
    public IList<OrderItem> OrderItemList  
    { get; set; }  
}  

页面:

<!--     用户控件      -->  
<%@ Control Language="C#" AutoEventWireup="true" CodeFile="MyControl.ascx.cs" Inherits="MyControl" %>  
<script type="text/C#" runat="server">      
	protected void Page_Load(object sender, EventArgs e)  
    {  
        GridView1.DataSource = GetList();  
        GridView1.DataBind();  
    }  
    
      //模拟数据源
	protected IList<OrderItem> GetList()  
    {  
        IList<OrderItem> list = new List<OrderItem>();  
        OrderItem orderItem = new OrderItem("1", "Asus N75S", 8800, 2);  
        list.Add(orderItem);  
        ..........  
        return list;  
    }  
    
    //自定义委托
	public delegate void MyDelegate(object sender,MyEventArgs myEventArgs);  
    //自定义事件
	public event MyDelegate MyEvent;  
        
    //按下Button时激发自定义事件    
	protected void btn_click(object sender, EventArgs e)  
    {  
        if (MyEvent != null)  
        {  
            MyEventArgs myEventArgs = new MyEventArgs(labelName.Text, labelAddress.Text, labelTel.Text  
                , labelOrderCode.Text, GetList());  
            MyEvent(this,myEventArgs);  
        }  
    }  
</script>  
<div>  
    Name : <asp:Label ID="labelName" runat="server">Leslie</asp:Label><br />  
    Address : <asp:Label ID="labelAddress" runat="server">ZhongShan University 2A 501</asp:Label><br />  
    Tel : <asp:Label ID="labelTel" runat="server">13660123456</asp:Label><br />  
    Order Code : <asp:Label ID="labelOrderCode" runat="server">A12012031223B0030</asp:Label><br /><br />  
    <asp:GridView ID="GridView1" runat="server" AutoGenerateColumns="False" CellPadding="5">  
    <Columns>  
        <asp:BoundField DataField="OrderItemID" HeaderText="ID"/>  
        <asp:BoundField DataField="Goods" HeaderText="Goods"/>  
        <asp:BoundField DataField="Price" HeaderText="Price"/>  
        <asp:BoundField DataField="Count" HeaderText="Count"/>  
    </Columns>  
    </asp:GridView>  
    <br />  
    <asp:Button ID="btn" runat="server" Text="Account" OnClick="btn_click"/>  
</div>  
        
<!--    页面处理      -->  
<%@ Page Language="C#" AutoEventWireup="true" CodeFile="Default2.aspx.cs" Inherits="Default2" %>  
<%@ Register Src="~/MyControl.ascx" TagPrefix="ascx" TagName="myControl" %>  
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">  
    
<html xmlns="http://www.w3.org/1999/xhtml">  
<head runat="server">  
    <title></title>  
    <script type="text/C#" runat="server">         
		//在页面定义用户控件MyEvent事件的处理方法         
		protected void myControl_Click(object sender,MyEventArgs e)  
        {  
            //计算订单总体价             
			double totalPrice=0;  
            IList<OrderItem> list=e.OrderItemList;  
            foreach(OrderItem item in list)  
                totalPrice+=item.Price*item.Count;  
            //展示订单号及总体费用  
            labelOrderCode.Text = e.OrderCode;  
            labelTotalPrice.Text = totalPrice.ToString();  
        }  
    </script>  
</head>  
<body>  
    <form id="form1" runat="server">  
    <div>  
        <ascx:myControl ID="myControl" runat="server" OnMyEvent="myControl_Click"></ascx:myControl>  
        <br />  
        OrderCode : <asp:Label ID="labelOrderCode" runat="server"></asp:Label><br />  
        TotalPrice :  <asp:Label ID="labelTotalPrice" runat="server"></asp:Label>  
    </div>  
    </form>  
</body>  
</html>

运行结果

若对自定义事件不太熟悉的朋友很多时候会使用 UserControl.FindControl的方式获取用户控件中的属性,但当你深入了解自定义事件的开发过程以后,就能有效简化开发的过程。

五、Lambda 表达式

5.1 Lambda 的意义

在Framework 2.0 以前,声明委托的唯一方法是通过方法命名,从Framework 2.0 起,系统开始支持匿名方法。 通过匿名方法,可以直接把一段代码绑定给事件,因此减少了实例化委托所需的编码系统开销。 而在 Framework 3.0 开始,Lambda 表达式开始逐渐取代了匿名方法,作为编写内联代码的首选方式。总体来说,Lambda表达式的作用是为了使用更简单的方式来编写匿名方法,彻底简化委托的使用方式。

5.2 回顾匿名方法的使用

匿名方法的使用已经在4.4节简单介绍过,在此回顾一下。
使用下面的方式,可以通过匿名方法为Button的Click事件绑定处理方法。

static void Main(string[] args)  
{  
    Button btn = new Button();  
    btn.Click+=delegate(object obj,EventArgs e){  
        MessageBox.Show("Hello World !");  
    };  
}

总是使用 delegate(){…} 的方式建立匿名方法,令人不禁感觉郁闷。于是从Framework 3.0 起, Lambda 表达式开始出现。

5.3 简单介绍泛型委托

在介绍 Lambda 表达式前,先介绍一下常用的几个泛型委托。

5.3.1 泛型委托 Predicate

早在Framework 2.0 的时候,微软就为 List 类添加了 Find、FindAll 、ForEach 等方法用作数据的查找。

public T Find ( Predicate<T> match)  
public List<T> FindAll(Predicate<T>  match)

在这些方法中存在一个Predicate 表达式,它是一个返回bool的泛型委托,能接受一个任意类型的对象作为参数。

public delegate bool Predicate<T>(T obj)

在下面例子中,Predicate 委托绑定了参数为Person类的方法Match作为查询条件,然后使用 FindAll 方法查找到合适条件的
List 集合。

class Program  
{  
    static void Main(string[] args)  
    {  
        List<Person> list = GetList();  
        //绑定查询条件          
		Predicate<Person> predicate = new Predicate<Person>(Match);  
        List<Person> result = list.FindAll(predicate);  
        Console.WriteLine(“Person count is :+ result.Count);  
        Console.ReadKey();  
    }  
    //模拟源数据         
	static List<Person> GetList()  
    {  
        var personList = new List<Person>();  
        var person1 = new Person(1,"Leslie",29);  
        personList.Add(person1);  
        ........  
        return personList;  
    }  
    //查询条件  
	static bool Match(Person person)  
    {  
        return person.Age <= 30;  
    }  
}  
   
public class Person  
{  
    public Person(int id, string name, int age)  
    {  
        ID = id;  
        Name = name;  
        Age = age;  
    }  
   
    public int ID  
    { get; set; }  
    public string Name  
    { get; set; }  
    public int Age  
    { get; set; }  
}

5.3.2 泛型委托 Action

Action 的使用方式与 Predicate 相似,不同之处在于 Predicate 返回值为 bool , Action
的返回值为 void。
Action 支持0~16个参数,可以按需求任意使用。

public delegate void Action()  
    
public delegate void Action<T1>(T1 obj1)  
    
public delegate void Action<T1,T2>T1 obj1, T2 obj2)  
    
public delegate void Action<T1,T2,T3>T1 obj1, T2 obj2,T3 obj3)  

............  

public delegate void Action<T1,T2,T3,......,T16>T1 obj1, T2 obj2,T3 obj3,......,T16 obj16)
static void Main(string[] args)  
{  
    Action<string> action=ShowMessage;  
    action("Hello World");  
    Console.ReadKey();  
}  
   
static void ShowMessage(string message)  
{  
	MessageBox.Show(message);  
}

5.3.3 泛型委托 Func

委托 Func 与 Action 相似,同样支持 0~16 个参数,不同之处在于Func 必须具有返回值

public delegate TResult Func<TResult>()  

public delegate TResult Func<T1,TResult>(T1 obj1)  

public delegate TResult Func<T1,T2,TResult>T1 obj1,T2 obj2)  

public delegate TResult Func<T1,T2,T3,TResult>T1 obj1,T2 obj2,T3 obj3)  

............  

public delegate TResult Func<T1,T2,T3,......,T16,TResult>T1 obj1,T2 obj2,T3 obj3,......,T16 obj16)
static void Main(string[] args)  
{  
    Func<double, bool, double> func = Account;  
    double result=func(1000, true);  
    Console.WriteLine("Result is : "+result);  
    Console.ReadKey();  
}  
   
static double Account(double a,bool condition)  
{  
	if (condition)  
		return a * 1.5;  
	else  
		return a * 2;  
}

5.4 揭开 Lambda 神秘的面纱

Lambda 的表达式的编写格式如下:

 x=> x * 1.5

当中 “ => ” 是 Lambda 表达式的操作符,在左边用作定义一个参数列表,右边可以操作这些参数。

例子一, 先把 int x 设置 1000,通过 Action 把表达式定义为 x=x+500 ,最后通过 Invoke 激发委托

static void Main(string[] args)  
{  
    int x = 1000;  
    Action action = () => x = x + 500;  
    action.Invoke();  
   
    Console.WriteLine("Result is : " + x);  
    Console.ReadKey();  
}

例子二,通过 Action 把表达式定义 x=x+500, 到最后输入参数1000,得到的结果与例子一相同。
注意,此处Lambda表达式定义的操作使用 { } 括弧包括在一起,里面可以包含一系列的操作。

static void Main(string[] args)  
{  
    Action<int> action = (x) =>  
    {  
        x = x + 500;  
        Console.WriteLine("Result is : " + x);  
    };  
    action.Invoke(1000);  
    Console.ReadKey();  
}

例子三,定义一个Predicate,当输入值大约等于1000则返回 true , 否则返回
false。与5.3.1的例子相比,Predicate的绑定不需要显式建立一个方法,而是直接在Lambda表达式里完成,简洁方便了不少。

static void Main(string[] args)  
{  
    Predicate<int> predicate = (x) =>  
    {  
        if (x >= 1000)  
            return true;  
        else  
            return false;  
    };  
    bool result=predicate.Invoke(500);  
    Console.ReadKey();  
}

例子四,在计算商品的价格时,当商品重量超过30kg则打9折,其他按原价处理。此时可以使用Func<double,int,double>,参数1为商品原价,参数2为商品重量,最后返回值为
double 类型。

static void Main(string[] args)  
{  
    Func<double, int, double> func = (price, weight) =>  
    {  
        if (weight >= 30)  
            return price * 0.9;  
        else  
            return price;  
    };  
    double totalPrice = func(200.0, 40);  
    Console.ReadKey();  
}

例子五,使用Lambda为Button定义Click事件的处理方法。与5.2的例子相比,使用Lambda比使用匿名方法更加简单。

static void Main(string[] args)  
{  
    Button btn = new Button();  
    btn.Click += (obj, e) =>  
    {  
        MessageBox.Show("Hello World!");  
    };  
    Console.ReadKey();  
}

例子六,此处使用5.3.1的例子,在List的FindAll方法中直接使用Lambda表达式。
相比之下,使用Lambda表达式,不需要定义Predicate对象,也不需要显式设定绑定方法,简化了不工序。

class Program  
{  
    static void Main(string[] args)  
    {  
        List<Person> personList = GetList();  
        //查找年龄少于30年的人  
        List<Person> result=personList.FindAll((person) => person.Age =< 30);  
        Console.WriteLine("Person count is : " + result.Count);  
        Console.ReadKey();  
    }  
   
    //模拟源数据       
	static List<Person> GetList()  
    {  
        var personList = new List<Person>();  
        var person1 = new Person(1,"Leslie",29);  
        personList.Add(person1);  
        .......  
        return personList;  
    }  
}  
    
public class Person  
{  
    public Person(int id, string name, int age)  
    {  
        ID = id;  
        Name = name;  
        Age = age;  
    }  
    
    public int ID  
    { get; set; }  
    public string Name  
    { get; set; }  
    public int Age  
    { get; set; }  
}

当在使用LINQ技术的时候,到处都会弥漫着 Lambda 的身影,此时更能体现 Lambda 的长处。
但 LINQ 涉及到分部类,分部方法,IEnumerable,迭代器等多方面的知识,这些已经超出本章的介绍范围。
通过这一节的介绍,希望能够帮助大家更深入地了解 Lambda 的使用。

本章小结

本章主要介绍了委托(Delegate)的使用,委托对象是一个派生自 System.MultcastDelegate 的类,它能通过 Invoke 方式进行同步调用,也可以通过 BeginInvoke,EndInvoke 方式实现异步调用。而事件(Event)属于一种特殊的委托,它与委托类型同步使用,可以简化的开发过程。 最后,本文还介绍了匿名方法的使用方式,以及 Lambda 表达式的由来。

版权申明:本文来源于网友收集或网友提供,如果有侵权,请转告版主或者留言,本公众号立即删除。

出处: http://www.cnblogs.com/leslies2/archive/2012/03/22/2389318.html

本文由mdnice多平台发布

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

YSの陈憨憨

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

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

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

打赏作者

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

抵扣说明:

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

余额充值