• C#秘密武器之委托


    在C#的世界里,委托是无处不在,尤其在.NET自己封装的框架里到处都有其身影,所以掌握委托就很有必要了!那什么是委托呢?其实委托就是一种数据类型,跟int等东东差不多,只不过在使用时要自己先去构建一个委托数据类型(不像int微软已为你准备好),然后声明一个委托类型的变量,并为其赋值,赋值的对象只能是方法,最后通过委托变量就可以进行方法调用了!

    委托的简单使用

    如下定义了一个委托类型 - Calculator:

    delegate int Calculator (int x);

    此委托适用于任何有着int返回类型和一个int类型参数的方法,如:

    static int Double (int x) { return x * 2; }

    创建一个委托实例,将该此方法赋值给该委托实例:

    Calculator c = new Calculator(Double);

    也可以简写成:

    Calculator c = Double;

    这个方法可以通过委托调用:

    int result = c(2);

    下面是完整代码:

    delegate int Calculator(int x);
    
    class Program {
    
        static int Double(int x) { return x * 2; }
        static void Main(string[] args) {
            Calculator c = Double;
            int result = c(2);
    
            Console.Write(result);
            Console.ReadKey();
        }
    }

    一、多路委托

    委托类实际继承于MulticastDelegate,这使委托对象支持多路委托,即委托对象可以绑定多个方法。当输入参数后,每个方法会“按顺序”执行!这里不建议委托方法有返回值,即使有返回值,多路委托中的方法也只能返回最后一个方法的返回值!

    例如:

    MyDelegate d = MyMethod1;
    // “+=” 用来添加,同理“-=”用来移除。
    d += MyMethod2;
    // d -= MyMethod2 

    调用时,按照方法被添加的顺序依次执行。注意,对于委托,+= (实质是调用的Delegate的Combine方法)和 -= (实质是调用的Delegate的Remove方法)

    MyDelegate d;
    d += MyMethod1;// 相当于MyDelegate d = MyMethod1;

    我们先定义一个委托(ProgressReporter),然后定义一个匹配方法(Match)来执行该委托中的所有方法。如下:

    public delegate void ProgressReporter(int percentComplete);
    
    public class Utility {
        public static void Match(ProgressReporter p) {
            if (p != null) {
                for (int i = 0; i <= 10; i++) {
                    p(i * 10);
                    System.Threading.Thread.Sleep(100);
                }
            }
        }
    }

    然后我们需要两个监视进度的方法,一个把进度写到Console,另一个把进度写到文件。如下:

    class Program {
        static void Main(string[] args) {
            ProgressReporter p = WriteProgressToConsole;
            p += WriteProgressToFile;
            Utility.Match(p);
            Console.WriteLine("Done.");
            Console.ReadKey();
        }
    
        static void WriteProgressToConsole(int percentComplete) {
            Console.WriteLine(percentComplete+"%");
        }
        static void WriteProgressToFile(int percentComplete) {
            System.IO.File.AppendAllText("progress.txt", percentComplete + "%");
        }
    }

    运行结果:

    注意:同一类型的委托变量可以相加减!

    二、委托体现的插件式编程思想

    其实就是把委托(方法)作为一种方法的参数来进行传递,由于同一委托可以接收多种不同实现的方法(插件),从而实现了一种插件式编程,实现动态的扩展。

    例如,我们有一个Utility类,这个类实现一个通用方法(Calculate),用来执行任何有一个整型参数和整型返回值的方法。这样说有点抽象,下面来看一个例子:

    delegate int Calculator(int x);
    
    class Program {
    
        static int Double(int x) { return x * 2; }
        static void Main(string[] args) {
            int[] values = { 1,2,3,4};
            Utility.Calculate(values, Double);
    
            foreach (int i in values)
                Console.Write(i + " "); // 2 4 6 8
    
            Console.ReadKey();
        }
    }
    
    class Utility {
        public static void Calculate(int[] values, Calculator c) {
            for (int i = 0; i < values.Length; i++)
                values[i] = c(values[i]);
        }
    }

    这个例子中的Utility是固定不变的,程序实现了整数的Double功能。我们可以把这个Double方法看作是一个插件,如果将来还要实现诸如求平方、求立方的计算,我们只需向程序中不断添加插件就可以了。

    如果Double方法是临时的,只调用一次,若在整个程序中不会有第二次调用,那么我们可以在Main方法中更简洁更灵活的使用这种插件式编程,无需先定义方法,使用λ表达式即可,如:

    ...
    Utility.Calculate(values, x => x * 2);
    ...

    三、静态方法和实例方法对于委托的区别

    当一个类的实例的方法被赋给一个委托对象时,在上下文中不仅要维护这个方法,还要维护这个方法所在的实例。System.Delegate 类的Target属性指向的就是这个实例。举个例子:

    class Program {
        static void Main(string[] args) {
            X x = new X();
            ProgressReporter p = x.InstanceProgress;
            p(1);
            Console.WriteLine(p.Target == x); // True
            Console.WriteLine(p.Method); // Void InstanceProgress(Int32)
        }
    
        static void WriteProgressToConsole(int percentComplete) {
            Console.WriteLine(percentComplete+"%");
        }
        static void WriteProgressToFile(int percentComplete) {
            System.IO.File.AppendAllText("progress.txt", percentComplete + "%");
        }
    }
    
    class X {
        public void InstanceProgress(int percentComplete) {
            // do something
        }
    }

    但对于静态方法,System.Delegate 类的Target属性是Null,所以将静态方法赋值给委托时性能更优。

    四、泛型委托

    如果你知道泛型,那么就很容易理解泛型委托,说白了就是含有泛型参数的委托,例如:

    public delegate T Calculator<T> (T arg);

    我们可以把前面的例子改成泛型的例子,如下:

    public delegate T Calculator<T>(T arg);
    
    class Program {
    
        static int Double(int x) { return x * 2; }
        static void Main(string[] args) {
            int[] values = { 1, 2, 3, 4 };
            Utility.Calculate(values, Double);
    
            foreach (int i in values)
                Console.Write(i + " "); // 2 4 6 8
    
            Console.ReadKey();
        }
    }
    
    class Utility {
        public static void Calculate<T>(T[] values, Calculator<T> c) {
            for (int i = 0; i < values.Length; i++)
                values[i] = c(values[i]);
        }
    }

    Func委托

    委托 Func 支持 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;
            }

    Action委托

    Action<T> 的返回值为 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);
            }

    Predicate<T>委托

    Predicate只有一个参数,且返回值总是为bool类型!

    public delegate bool Predicate<T>(T obj)

    internal class PredicateDelegate
        {
            public bool PredicateMethod(int x )
            {
                return x > 50;
            }
        }
         PredicateDelegate predicateDelegate = new PredicateDelegate();
          // 只有一个参数 并返回bool 值
         Predicate<int> predicate = predicateDelegate.PredicateMethod;
         bool results =predicate(60);
         Console.WriteLine(results);

    Comparison委托


    public delegate int Comparison<in T>(T x, T y)

    为返回int类型的内置委托。T是要比较的对象的类型,而返回值是一个有符号整数,指示 x 与 y 的相对值,如下表所示:

    含义

    小于 0

    x 小于 y。

    0

    x 等于 y。

    大于 0

    x 大于 y。

      此委托由 Array 类的 Sort<T>(T[], Comparison<T>) 方法重载和 List<T> 类的 Sort(Comparison<T>) 方法重载使用,用于对数组或列表中的元素进行排序

    五、匿名方法和Lambda表达式

    匿名方法

    如果某个委托变量需要绑定的方法只用一次的话,其实是没有必要为方法起名的,直接用匿名方法会比较好!我们总是使用 delegate(){......} 的方式建立匿名方法!

    using System;
    using System.Collections.Generic;
    using System.Linq;
    using System.Text;
    
    namespace DelegateSamples
    {
        //声明一个委托,参数为string,无返回值
        delegate void DelSamples(string msg);
        class Program
        {
            static void Main(string[] args)
            {
              //匿名委托
              DelSamples delSample4 = delegate(string msg)
              {
                  Console.WriteLine("你好,我是{0}", msg);
              };
              delSample4("KoalaStudio");
    
              //利用Lambda表达式实现匿名委托
              DelSamples delSample5 = (string msg) => {
                 Console.WriteLine("你好,我是{0}", msg);
              };
              delSample5("KoalaStudio");
    
              Console.ReadKey();
            }
    
        }
    }

    Lambda表达式

    匿名方法的优雅写法就是Lambda表达式!

    注意事项:

    ①Lambda表达式中的参数列表(参数数量、类型和位置)必须与委托相匹配

    ②表达式中的参数列表不一定需要包含类型,除非委托有ref或out关键字(此时必须显示声明);

    如果没有参数,必须使用一组空的圆括号

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

    六、异步委托

    public delegate int Ad(int x,int y);
            static void Main(string[] args)
            {
                xu xus = new xu();
                Ad a = new Ad(Add);
                Console.WriteLine(a(3, 3));
                Console.WriteLine("start");
                Console.ReadLine();
            }
    static int Add(int x, int y)
            {
                Thread.Sleep(2000);
                return x + y;
            }

    运行这段代码 会先停顿2秒钟之后再显示6 和start 因为我使用了sleep这个方法 它使该线程休眠2秒钟,所以会在2秒之后显示信息,但是这对用户体验来说是非常糟糕的,那我们怎么改善呢?看看如下代码

    public delegate int Ad(int x,int y);
            static void Main(string[] args)
            {
                xu xus = new xu();
                Ad a = new Ad(Add);
                Console.WriteLine(a(3, 3));
               // Console.WriteLine("start");
               IAsyncResult isa= a.BeginInvoke(3, 3, null, null);
               while (!isa.IsCompleted)
               { 
                Console.WriteLine("未完成");
               }
              int s= a.EndInvoke(isa);
              Console.WriteLine(s.ToString());
                 Console.ReadLine();
            }
             static int Add(int x, int y)
            {
                Thread.Sleep(2000);
                return x + y;
            }
            static int ex(int x, int y)
            {
                //Thread.Sleep(5000);
                return x - y;
            }

    这里我们使用了begininvoke方法来异步执行 委托方法返回一个IAsyncResult 类型的值 代表委托执行的状态,使用一个while循环 来判断IsCompleted 如果没有完成异步调用则不断显示“未完成” 如果完成endinvoke 则返回结果。但是这里需要不断的询问操作完成状态 那么我们怎样让委托异步调用完成之后主动通知我们呢? 看看如下代码

    public delegate int Ad(int x,int y);
            static void Main(string[] args)
            {
                xu xus = new xu();
                Ad a = new Ad(Add);
                Console.WriteLine(a(3, 3));
                IAsyncResult isa= a.BeginInvoke(3, 3, new AsyncCallback(call), "edit by xyl");
                //执行你想执行的代码 这里我们还是用IsCompleted来代替
               while (!isa.IsCompleted)
               { 
                Console.WriteLine("未完成");
               }
                Console.ReadLine();
            }
            static void call(IAsyncResult isa)
            {
                AsyncResult ar = (AsyncResult)isa;
                Ad a = (Ad)ar.AsyncDelegate;
                Console.WriteLine("this is {0},{1}",a.EndInvoke(isa),ar.AsyncState);
            }
    
            static int Add(int x, int y)
            {
                Thread.Sleep(2000);
                return x + y;
            }
            static int ex(int x, int y)
            {
                //Thread.Sleep(5000);
                return x - y;
            }
        }

    这里我们使用了一个call方法 注意它是没有返回值的。把IAsyncResult转换成AsyncResult注意少了个I然后转换成AD 类型的委托 最后endinvoke 来返回值 这样在委托异步执行完成之后会自动通知方法。

    注意:回调函数是在 ThreadPool线程上进行的,因此主线程将继续执行。ThreadPool线程是后台线程,这些线程不会在主线程结束后保持应用程序的运行,因此主线程必须休眠足够长的时间以便回调完成。我们也可以在主线程完成操作后调用IsCompleted属性判断委托函数是否完成。

    七、事件

    事件其实就是某种类型的委托,在给事件赋值时,用符合该委托的方法就行!

    public delegate void PriceChangedHandler (decimal oldPrice, decimal newPrice);
    public class IPhone6
    {
        public event PriceChangedHandler PriceChanged;
    }

    事件的使用和委托完全一样,只是多了些约束。下面是一个简单的事件使用例子:

    public delegate void PriceChangedHandler(decimal oldPrice, decimal newPrice);
    
    public class IPhone6 {
        decimal price;
        public event PriceChangedHandler PriceChanged;
        public decimal Price {
            get { return price; }
            set {
                if (price == value) return;
                decimal oldPrice = price;
                price = value;
                // 如果调用列表不为空,则触发。
                if (PriceChanged != null)
                    PriceChanged(oldPrice, price);
            }
        }
    }
    
    class Program {
        static void Main() {
            IPhone6 iphone6 = new IPhone6() { Price = 5288 };
            // 订阅事件
            iphone6.PriceChanged += iphone6_PriceChanged;
    
            // 调整价格(事件发生)
            iphone6.Price = 3999;
    
            Console.ReadKey();
        }
    
        static void iphone6_PriceChanged(decimal oldPrice, decimal price) {
            Console.WriteLine("年终大促销,iPhone 6 只卖 " + price + " 元, 原价 " + oldPrice + " 元,快来抢!");
        }
    }

    运行结果:

    有人可能会问,如果把上面的event关键字拿掉,结果不是一样的吗,到底有何不同?

    没错可以用事件的地方就一定可以用委托代替。

    但事件有一系列规则和约束用以保证程序的安全可控,事件只有 += 和 -= 操作,这样订阅者只能有订阅或取消订阅操作,没有权限执行其它操作。如果是委托,那么订阅者就可以使用 = 来对委托对象重新赋值(其它订阅者全部被取消订阅),甚至将其设置为null,甚至订阅者还可以直接调用委托,这些都是很危险的操作,广播者就失去了独享控制权。

    事件保证了程序的安全性和健壮性。

    事件的标准模式

    .NET 框架为事件编程定义了一个标准模式。设定这个标准是为了让.NET框架和用户代码保持一致。System.EventArgs是标准模式的核心,它是一个没有任何成员,用于传递事件参数的基类。
    按照标准模式,我们对于上面的iPhone6示例进行重写。首先定义EventArgs:

    public class PriceChangedEventArgs : EventArgs {
        public readonly decimal OldPrice;
        public readonly decimal NewPrice;
        public PriceChangedEventArgs(decimal oldPrice, decimal newPrice) {
            OldPrice = oldPrice;
            NewPrice = newPrice;
        }
    }

    然后为事件定义委托,必须满足以下条件:

    • 必须是 void 返回类型;
    • 必须有两个参数,且第一个是object类型,第二个是EventArgs类型(的子类);
    • 它的名称必须以EventHandler结尾。

    由于考虑到每个事件都要定义自己的委托很麻烦,.NET 框架为我们预定义好一个通用委托System.EventHandler<TEventArgs>:

    public delegate void EventHandler<TEventArgs> (object source, TEventArgs e) where TEventArgs : EventArgs;

    如果不使用框架的EventHandler<TEventArgs>,我们需要自己定义一个:

    public delegate void PriceChangedEventHandler (object sender, PriceChangedEventArgs e);

    如果不需要参数,可以直接使用EventHandler(不需要<TEventArgs>)。有了EventHandler<TEventArgs>,我们就可以这样定义示例中的事件:

    public class IPhone6 {
        ...
        public event EventHandler<PriceChangedEventArgs> PriceChanged;
        ...
    }

    最后,事件标准模式还需要写一个受保护的虚方法来触发事件,这个方法必须以On为前缀,加上事件名(PriceChanged),还要接受一个EventArgs参数,如下:

    public class IPhone6 {
        ...
        public event EventHandler<PriceChangedEventArgs> PriceChanged;
        protected virtual void OnPriceChanged(PriceChangedEventArgs e) {
            if (PriceChanged != null) PriceChanged(this, e);
        }
        ...
    }

    下面给出完整示例:

    public class PriceChangedEventArgs : System.EventArgs {
        public readonly decimal OldPrice;
        public readonly decimal NewPrice;
        public PriceChangedEventArgs(decimal oldPrice, decimal newPrice) {
            OldPrice = oldPrice;
            NewPrice = newPrice;
        }
    }
    
    public class IPhone6 {
        decimal price;
        public event EventHandler<PriceChangedEventArgs> PriceChanged;
    
        protected virtual void OnPriceChanged(PriceChangedEventArgs e) {
            if (PriceChanged != null) PriceChanged(this, e);
        }
    
        public decimal Price {
            get { return price; }
            set {
                if (price == value) return;
                decimal oldPrice = price;
                price = value;
                // 如果调用列表不为空,则触发。
                if (PriceChanged != null)
                    OnPriceChanged(new PriceChangedEventArgs(oldPrice, price));
            }
        }
    }
    
    class Program {
        static void Main() {
            IPhone6 iphone6 = new IPhone6() { Price = 5288M };
            // 订阅事件
            iphone6.PriceChanged +=iphone6_PriceChanged;
    
            // 调整价格(事件发生)
            iphone6.Price = 3999;
            Console.ReadKey();
        }
    
        static void iphone6_PriceChanged(object sender, PriceChangedEventArgs e) {
            Console.WriteLine("年终大促销,iPhone 6 只卖 " + e.NewPrice + " 元, 原价 " + e.OldPrice + " 元,快来抢!");
        }
    }

    运行结果:

     

  • 相关阅读:
    Java 简单算法--打印乘法口诀(只使用一次循环)
    Java简单算法--求100以内素数
    ubuntu 16.04 chrome flash player 过期
    java 网络API访问 web 站点
    java scoket (UDP通信模型)简易聊天室
    leetcode1105 Filling Bookcase Shelves
    leetcode1140 Stone Game II
    leetcode1186 Maximum Subarray Sum with One Deletion
    leetcode31 Next Permutation
    leetcode834 Sum of Distances in Tree
  • 原文地址:https://www.cnblogs.com/WeiGe/p/4217600.html
Copyright © 2020-2023  润新知