代码改变世界

C#拾遗系列(1):委托

2008-06-11 14:43  敏捷的水  阅读(777)  评论(2编辑  收藏  举报

一、委托概述

委托具有以下特点:

  • 委托类似于 C++ 函数指针,但它们是类型安全的。

  • 委托允许将方法作为参数进行传递。

  • 委托可用于定义回调方法。

  • 委托可以链接在一起;例如,可以对一个事件调用多个方法。

  • 方法不必与委托签名完全匹配。(委托中的协变和逆变)

  • C# 2.0 版引入了匿名方法的概念,此类方法允许将代码块作为参数传递,以代替单独定义的方法。C# 3.0 引入了 Lambda 表达式,利用它们可以更简练地编写内联代码块。匿名方法和 Lambda 表达式(在某些上下文中)都可编译为委托类型。这些功能统称为匿名函数

二、委托申明和使用示例

using System;

using System.Collections.Generic;

using System.Linq;

using System.Text;

 

//Description: 委托三种声明方式.net1.0到.net3.0

 

namespace NetTest

    public class TestDelegateNet1To3

    {

        #region 不带返回值的委托

        delegate void TestDelegate(string s);

        public void TestAllDelegate()

        {

            //.net 1.0

            TestDelegate en = new TestDelegate(M);

            en("jack");

            TestDelegate china = China;

            china("wangdeshui");

 

            //.net 2.0

            TestDelegate delegateNet2 = delegate(string s) { Console.Out.WriteLine(s + ":这是一个2.0的方式"); };

            delegateNet2("2.0 delegate declare");

 

            //.net 3.0

            TestDelegate delegateNet3 = (string s) => { Console.Out.WriteLine(s + ":这是3.0的方式"); };

            delegateNet3("3.0 delegate declare");

 

            TestReturn(); 

 

            Func<int, bool> d = (n) => { return n > 10 ? true : false; };

            Console.Out.WriteLine("func<int, bool>:" + d(20));

 

 

        }

 

        void M(string s)

        {

            Console.Out.WriteLine(s + ":Good Morning");

        }

 

        void China(string s)

        {

            Console.Out.WriteLine(s + ":早上好");

        }

        #endregion

 

 

        #region 带返回值的委托测试

 

        delegate bool TestDelegateReturn(int i);

        void TestReturn()

        {

            TestDelegateReturn Net10 = new TestDelegateReturn(IsBig);

            bool i = Net10(11);

            Console.Out.WriteLine("Net10:" + i);

 

            TestDelegateReturn Net20 = delegate(int j)

            {

                return (j > 10 ? true : false);

 

            };

 

            bool test20 = Net20(5);

            Console.Out.WriteLine("Net20:" + test20);

 

            TestDelegateReturn Net30 = (int k) => { return (k > 10 ? true : false); };

            bool test30 = Net30(50);

            Console.Out.WriteLine("Net30:" + test30);

 

        }

 

        bool IsBig(int i)

        {

            return i > 10 ? true : false;

 

        }

 

        #endregion

    }

}

 

三、多播委托

调用委托时,它可以调用多个方法。这称为多路广播。若要向委托的方法列表(调用列表)中添加额外的方法,只需使用加法运算符或加法赋值运算符(“+”或“+=”)添加两个委托。

注意:

作为委托参数传递的方法必须与委托声明具有相同的签名。
委托实例可以封装静态或实例方法。
尽管委托可以使用 out 参数,但建议您不要将其用于多路广播事件委托,因为您无法知道哪个委托将被调用。

多播委托传递值类型时,对参数的修改不会传到下一个
多播委托传递引用类型时,对引用参数的修改会传到下一个

示例:

public class TestMultiCastDelegate

    {

        public delegate void Del(string message);

        delegate void DelRef(Person p);

        void MethodA(string s)

        {

            Console.Out.WriteLine("Method A:"+s);

        }

 

        void MethodB(string s)

        {

            s = "good";

            Console.Out.WriteLine("Method B:" + s);

        }

 

        void MethodC(string s)

        {

            Console.Out.WriteLine("Method C:" + s);

        }

 

        /*

        多播委托传递值类型时,对参数的修改不会传到下一个

        多播委托传递引用类型时,对引用参数的修改会传到下一个

        *

        * */

 

        public void Test()

        {

            //多播委托

            Del d1 = MethodA;

            Del d2 = MethodB;

            Del d3=MethodC;

            Del AllDelegateMethod = d1 + d2;

            AllDelegateMethod += d3;

            AllDelegateMethod("Jack");

            /*

            output:

            ConsoleA: Jack

            ConsoleB: Jack

            ConsoleC:Jack           

            */

            Console.Out.WriteLine("---------------------");

            AllDelegateMethod -= d3;

            AllDelegateMethod("wangds");

 

 

            //测试引用类型

            DelRef dref1 = MethodRefA;

            DelRef dref2 = MethodRefB;

            DelRef Alldref = dref1 + dref2;

            Person p = new Person { Address = "USA", Name = "America" };

            Alldref(p);

        }

 

 

        //内嵌类

 

        class Person

        {

            public string Name { get; set; }

            public string Address { getset; }

 

        }

 

        void MethodRefA(Person p)

        {

            Console.Out.WriteLine(p.Name + "," + p.Address);

            Console.Out.WriteLine("----------MethodRefA---------");

            p.Name = "Jack";

            p.Address = "China";

 

            Console.Out.WriteLine(p.Name + "," + p.Address);

            Console.Out.WriteLine("----------MethodRefA---------");

        }

 

        void MethodRefB(Person p)

        {

            Console.Out.WriteLine("--------MethodRefB----------");

            Console.Out.WriteLine(p.Name + "," + p.Address);

            Console.Out.WriteLine("--------MethodRefB----------");

        }

    }

四、委托中的协变和逆变

委托中的协变: 就是允许方法的返回类型是委托的返回类型的子类

委托中的逆变,方法的参数是委托的参数的基类

示例:

#region 委托中的协变: 就是允许方法的返回类型是委托的返回类型的子类

    class Mammals

    {

    }

 

    class Dogs : Mammals

    {

    }

 

    class XieBianDelegate

    {

        // Define the delegate.

        public delegate Mammals HandlerMethod();

 

        public Mammals FirstHandler()

        {

            return null;

        }

 

        public Dogs SecondHandler()

        {

            return null;

        }

 

        void Test()

        {

            HandlerMethod handler1 = FirstHandler;

 

            // Covariance allows this delegate.

            HandlerMethod handler2 = SecondHandler;

        }

    }

    #endregion

 

 

    #region   委托中的逆变,方法的参数是委托的参数的基类

    class NiBianDelegate

    {

        public delegate Dogs HandlerNibianMethod(Dogs dogs);

        public Dogs FirstHandlerNibian(Mammals mammals)

        {

            return null;

        }

 

        void Test()

        {

            Dogs dogs = new Dogs();

            HandlerNibianMethod handNibian1 = FirstHandlerNibian;

 

        }

 

    }

    #endregion

五、委托综合示例(来自MSDN)

下面的示例阐释声明、实例化和使用委托。BookDB 类封装一个书店数据库,它维护一个书籍数据库。它公开 ProcessPaperbackBooks 方法,该方法在数据库中查找所有平装书,并对每本平装书调用一个委托。使用的 delegate 类型名为 ProcessBookDelegate。Test 类使用该类打印平装书的书名和平均价格。

委托的使用促进了书店数据库和客户代码之间功能的良好分隔。客户代码不知道书籍的存储方式和书店代码查找平装书的方式。书店代码也不知道找到平装书后将对平装书执行什么处理。

using System;

using System.Collections.Generic;

using System.Linq;

using System.Text;

 

namespace Bookstore

{

    using System.Collections;

 

    // Describes a book in the book list:

    public struct Book

    {

        public string Title;        // Title of the book.

        public string Author;       // Author of the book.

        public decimal Price;       // Price of the book.

        public bool Paperback;      // Is it paperback?

 

        public Book(string title, string author, decimal price, bool paperBack)

        {

            Title = title;

            Author = author;

            Price = price;

            Paperback = paperBack;

        }

    }

 

    // Declare a delegate type for processing a book:

    public delegate void ProcessBookDelegate(Book book);

 

    // Maintains a book database.

    public class BookDB

    {

        // List of all books in the database:

        ArrayList list = new ArrayList();

 

        // Add a book to the database:

        public void AddBook(string title, string author, decimal price, bool paperBack)

        {

            list.Add(new Book(title, author, price, paperBack));

        }

 

        // Call a passed-in delegate on each paperback book to process it:

        public void ProcessPaperbackBooks(ProcessBookDelegate processBook)

        {

            foreach (Book b in list)

            {

                if (b.Paperback)

                    // Calling the delegate:

                    processBook(b);

            }

        }

    }

}

 

 

 

 

// Using the Bookstore classes:

namespace BookTestClient

{

    using Bookstore;

 

    // Class to total and average prices of books:

    class PriceTotaller

    {

        int countBooks = 0;

        decimal priceBooks = 0.0m;

 

        internal void AddBookToTotal(Book book)

        {

            countBooks += 1;

            priceBooks += book.Price;

        }

 

        internal decimal AveragePrice()

        {

            return priceBooks / countBooks;

        }

    }

 

    // Class to test the book database:

  public  class TestBookDB

    {

        // Print the title of the book.

        void PrintTitle(Book b)

        {

            System.Console.WriteLine("   {0}", b.Title);

        }

 

        // Execution starts here.

        public void Test()

        {

            BookDB bookDB = new BookDB();

 

            // Initialize the database with some books:

            AddBooks(bookDB);

 

            // Print all the titles of paperbacks:

            System.Console.WriteLine("Paperback Book Titles:");

 

            // Create a new delegate object associated with the static

            // method Test.PrintTitle:

            bookDB.ProcessPaperbackBooks(PrintTitle);

 

            // Get the average price of a paperback by using

            // a PriceTotaller object:

            PriceTotaller totaller = new PriceTotaller();

 

            // Create a new delegate object associated with the nonstatic

            // method AddBookToTotal on the object totaller:

            bookDB.ProcessPaperbackBooks(totaller.AddBookToTotal);

 

            System.Console.WriteLine("Average Paperback Book Price: ${0:#.##}",

                    totaller.AveragePrice());

        }

 

        // Initialize the book database with some test books:

        static void AddBooks(BookDB bookDB)

        {

            bookDB.AddBook("The C Programming Language", "Brian W. Kernighan and Dennis M. Ritchie", 19.95m, true);

            bookDB.AddBook("The Unicode Standard 2.0", "The Unicode Consortium", 39.95m, true);

            bookDB.AddBook("The MS-DOS Encyclopedia", "Ray Duncan", 129.95m, false);

            bookDB.AddBook("Dogbert's Clues for the Clueless", "Scott Adams", 12.00m, true);

        }

    }

}