1. 程式人生 > >C#委託/Lambda表示式/事件

C#委託/Lambda表示式/事件

1.委託基本定義

委託用delegate定義,指定返回值和引數列表的函式型別,不包括資料,這些方法是不區分靜態或者非靜態的,可以引用一個委託例項也可以引用多個(廣播)。

可以任意修飾符,可以防止在類內部,也可以在類外部,不可以在函式內部定義。


給委託物件賦值時候需要對委託建構函式傳遞一個引數(具體方法的引用),或者將定義的方法直接賦值給委託物件這些方法不用傳遞引數先真正呼叫時候委託物件時候才需要將引數傳入,函式指標是可以執行時繫結的(遲繫結技術)。

2.委託泛型

除了自定義委託,還可以用委託模板,Action<in T1, in T2,...>可以宣告為帶0到8個泛型型別引數,沒有返回型別的方法

Func<in T, out TResult>可以定義0到16個泛型型別引數,有一個返回型別的方法
// 多型的委託是委託的重要作用之一,事件委託也是委託的重要作用
class BubbleSorter
    {
        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);
        }
    }

// 委託例項方法
   public static bool CompareSalary(Employee e1, Employee e2)
        {
            return e1.Salary < e2.Salary;
        }

// 呼叫端
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)
            };
            BubbleSorter.Sort(employees, Employee.CompareSalary);
            foreach (var employee in employees)
            {
                Console.WriteLine(employee);
            }
        }

3.委託多播+-,和使用GetInvocationList()返回委託例項

單個委託和委託陣列都是單播委託
如果要定義多播委託就要使用 + += - -=這些運算子,因為委託都是System.MulticastDelegate的類,System.MulticastDelegate派生自System.Delegate,System.MulticastDelegate可以把多個 委託加減連結運算子生成一個委託例項列表。

多播委託建議返回型別是void,如果有其它返回型別,那麼返回型別是最後一個委託例項的返回型別

    static void Main()
        {
            // 返回void相加的委託例項
            Action<double> operations = MathOperations.MultiplyByTwo;
            // 返回void求平方的委託例項
            operations += MathOperations.Square;

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

多播委託有一個缺點就是一個丟擲異常後面不會再執行了,為了避免丟擲異常終止,可以用GetInvocationList()方法返回委託物件來解決。
Action d1 = One;
 d1 += Two;
 Delegate[] delegates = d1.GetInvocationList();
 foreach (Action d in delegates)
 {
  }

4.匿名委託

委託物件 =delegate(引數){委託例項程式碼塊;}來賦值給委託物件。

static void Main()
        {
            string mid = ", middle part,";
            // 委託物件 用匿名的委託例項初始化,delegate(委託輸入引數) {委託例項程式碼,返回委託輸出型別}
            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"));
        }
匿名委託優點是減少編寫的程式碼,特別是減少了很多委託例項的命名;如果需要多次呼叫同一個方法那麼不要用匿名委託。
注意是匿名委託程式碼塊不能中間跳轉到程式碼塊外部,也不能從外部直接跳轉到匿名委託程式碼塊中。
匿名程式碼塊不能訪問不安全的程式碼,不能訪問匿名方法外部使用ref/out修飾的引數,其它外部變數可以用,因為委託呼叫時候才使用,用了外部變數需要注意意外的結果。

5.Lambda表示式

Lambda表示式用於型別是委託時簡化匿名委託方法的書寫,或者型別是Expression或Expression<T>時建立表示式樹。
delegate deObj = param => expression;
delegate void SimpleTest();
        static void Main()
        {
            // 多個引數就()裡面寫,多行語句就用{},有返回值用return
            SimpleTest simpleObj = () => { Console.WriteLine("Hello Lambda world."); return; };
            simpleObj();

            int someVal = 5;
            // 這裡只是定義委託方法,如果呼叫了外部變數,呼叫委託方法時外部變數被改變了會導致意外的結果
            Func<int, int> f = x => x + someVal;
            someVal = 7;
            Console.WriteLine(f(3));
        }

6.事件

事件其實就是函式指標實現,釋出程式先註冊事件處理函式(委託例項也叫監聽器),當事件發生時候(windows wndproc會不停的檢查鍵盤滑鼠執行緒等事件)那麼會呼叫釋出程式呼叫委託例項。

1)強型別的事件,一個釋出程式對應一個監聽器時候建議使用:

EventsSample例項:

CarDealer.cs

using System;

namespace Wrox.ProCSharp.Delegates
{
    // 提供了事件引數,所有偵聽器都可以使用該引數
    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 void NewCar(string car)
        {
            Console.WriteLine("CarDealer, new car {0}", car);
            if (NewCarInfo != null)
            {
                // CarInfoEventArgs事件引數來自於釋出者,函式由訂閱者提供
                // public delegate void EventHandler<TEventArgs>(object sender, TEventArgs e)
                NewCarInfo(this, new CarInfoEventArgs(car));
            }
        }
    }
}

Consumer.cs

using System;

namespace Wrox.ProCSharp.Delegates
{
    // 事件偵聽器,呼叫了釋出者事件的引數,提供委託(函式指標)例項給委託(函式指標)物件
    public class Consumer
    {
        private string name;

        public Consumer(string name)
        {
            this.name = name;
        }
        // public delegate void EventHandler<TEventArgs>(object sender, TEventArgs e)
        // CarInfoEventArgs 事件引數來自於釋出者,函式由訂閱者提供
        public void NewCarIsHere(object sender, CarInfoEventArgs e)
        {
            Console.WriteLine("{0}: car {1} is new", name, e.Car);
        }
    }
}

Program.cs

namespace Wrox.ProCSharp.Delegates
{
    class Program
    {
        static void Main()
        {
            var dealer = new CarDealer();

            var michael = new Consumer("Michael");
            // 將委託例項賦值給了委託物件
            dealer.NewCarInfo += michael.NewCarIsHere;
            // 事件發生,釋出者呼叫委託物件,釋出給訂閱者
            dealer.NewCar("Mercedes");

            var nick = new Consumer("Nick");
            // 增加訂閱者
            dealer.NewCarInfo += nick.NewCarIsHere;

            // 釋出者多播給多個訂閱者
            dealer.NewCar("Ferrari");

            // 減少訂閱者,如但是釋出者會仍然有一個引用而無法垃圾回收訂閱者資源,需要一個弱引用管理器來管理。
            dealer.NewCarInfo -= michael.NewCarIsHere;

            // 釋出者釋出給訂閱者
            dealer.NewCar("Toyota");
        }
    }
}

WeakEventsSample例項:

CarDealer.cs

using System;

namespace Wrox.ProCSharp.Delegates
{
    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));
            }
        }
    }
}

Consumer.cs

using System;
using System.Windows;

namespace Wrox.ProCSharp.Delegates
{
    // 繼承IWeakEventListener 為了將監聽器作為 WeakEventManager管理器類的傳入引數:
    // public static void AddListener(object source, IWeakEventListener listener);
    // public static void RemoveListener(object source, IWeakEventListener listener);
    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);
        }

        // IWeakEventListener函式,接受事件時候的處理介面方法,在釋出者真正呼叫時候才會進來
        bool IWeakEventListener.ReceiveWeakEvent(Type managerType, object sender, EventArgs e)
        {
            NewCarIsHere(sender, e as CarInfoEventArgs);
            return true;
        }
    }
}

WeakCarInfoEventManager.cs

using System.Windows;

namespace Wrox.ProCSharp.Delegates
{
    // 使用委託弱關聯管理器,需要實現函式:
    // public static void AddListener(object source, IWeakEventListener listener);
    // protected override void StartListening(object source);

    // public static void RemoveListener(object source, IWeakEventListener listener);
    // protected override void StopListening(object source);

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

      /**********************管理器內部實現邏輯****************************/
        protected static WeakCarInfoEventManager CurrentManager
        {
            get
            {
                // 父類WeakEventManager定義static WeakEventManager GetCurrentManager(Type managerType)
                WeakCarInfoEventManager manager = GetCurrentManager(typeof(WeakCarInfoEventManager)) as WeakCarInfoEventManager;
                // 單例設計模式,只返回一個物件
                if (manager == null)
                {
                    manager = new WeakCarInfoEventManager();
                    // 父類定義static void SetCurrentManager(Type managerType, WeakEventManager manager);
                    SetCurrentManager(typeof(WeakCarInfoEventManager), manager);
                }
                return manager;
            }
        }

        // 外部真正事件回撥時候進入這裡呼叫,例如:dealer.NewCar("xxx");
        protected void CarDealer_NewCarInfo(object sender, CarInfoEventArgs e)
        {
            // 傳遞事件,WeakEventManager函式
            // 這裡的CarDealer_NewCarInfo和傳入委託例項之間的關係,類似map對映一一對應的關係。呼叫委託時候也對應過來。
            DeliverEvent(sender, e);
        }

        // AddListener時候會進入,告訴釋出者委託物件加上,傳入的委託例項間接轉換為CarDealer_NewCarInfo的例項
        // 這裡的CarDealer_NewCarInfo和傳入委託例項之間的關係,類似map對映一一對應的關係。呼叫委託時候也對應過來。
        protected override void StartListening(object source)
        {
            (source as CarDealer).NewCarInfo += CarDealer_NewCarInfo;
        }

        // RemoveListener時候會進入,告訴釋出者委託物件減去,傳入的委託例項間接轉換為CarDealer_NewCarInfo的例項
        protected override void StopListening(object source)
        {
            (source as CarDealer).NewCarInfo -= CarDealer_NewCarInfo;
        }
    }
}

Program.cs

namespace Wrox.ProCSharp.Delegates
{
    class Program
    {
        static void Main()
        {
            var dealer = new CarDealer();
            var michael = new Consumer("Michael");
            // 新增到管理器,其中分發者會關聯到一個管理器間接的委託例項,
            // 該間接的委託例項和真正委託例項之間有一一對應關係。
            WeakCarInfoEventManager.AddListener(dealer, michael);
            // 釋出者產生事件,回撥WeakCarInfoEventManager的處理函式,其中該處理函式是間接的
            // 會分發轉換到真正的michael委託例項,實現間接的真正回撥偵聽器方法。
            dealer.NewCar("Mercedes");

            var nick = new Consumer("Nick");
            WeakCarInfoEventManager.AddListener(dealer, nick);
            dealer.NewCar("Ferrari");

            // 作用: 釋出者和監聽器之間就不是強型別關係,而是弱型別關係,移除監聽器後不再使用就會被垃圾回收器回收
            WeakCarInfoEventManager.RemoveListener(dealer, michael);
            dealer.NewCar("Toyota");
        }
    }
}