2

初识委托

委托的作用:Microsoft .NET Framework通过委托来提供回调函数机制。C和C++程序员长期以来利用"函数指针"将对方法的引用作为实参传给另一个方法。C#使用委托提供相似的功能,委托允许捕捉对方法的引用。

namespace delegation.simple
{
    // 定义一个委托
    internal delegate int Feedback(int value);
    class Program
    {
        // 委托方法1
        private static int FeedbackMethod1(int value)
        {
            Console.WriteLine("执行了委托方法1");
            return value;
        }

        // 委托方法2
        private int FeedbackMethod2(int value)
        {
            Console.WriteLine("执行了委托方法2");
            return value;
        }

        private static int Counter(int from, int to, Feedback fb)
        {
            int result = 0;
            for (int val = from; val <= to; val++)
            {
                if (fb != null)
                {
                    result += fb(val);//等价于fb.Invoke(val);

                }
            }
            return result; //result=6
        }


        // 为委托回调静态方法
        private static void StaticDelegateDemo()
        {
            Console.WriteLine("---Static Delegate Demo ---");
            int result = Counter(1, 3, new Feedback(Program.FeedbackMethod1));

            Console.WriteLine(result);
        }

        // 为委托回调实例方法
        private static void InstanceDelegateDemo()
        {
            Console.WriteLine("---Instance Delegate Demo ---");
            Program p = new Program();
            int result = Counter(1, 3, new Feedback(p.FeedbackMethod2));

            Console.WriteLine(result);
        }

        // 委托链
        private static void ChainDelegateDemo1(Program p)
        {
            Console.WriteLine("---Chain Delegate Demo ---");
            Feedback fb1 = new Feedback(FeedbackMethod1);
            Feedback fb2 = new Feedback(p.FeedbackMethod2);
            
            // fbChain相当于一个委托对象集合
            Feedback fbChain = null;
            fbChain += fb1;
            fbChain += fb2;

            int result1 = Counter(1, 3, fbChain);

            Console.WriteLine();
            fbChain -= new Feedback(FeedbackMethod1);
            int result2 = Counter(1, 3, fbChain);

            Console.WriteLine("{0} and {1}", result1, result2);
        }
        static void Main(string[] args)
        {
            StaticDelegateDemo();
            InstanceDelegateDemo();
            
            ChainDelegateDemo1(new Program());
        }
    }
}

用委托回调静态方法

请看看StaticDelegateDemo()

用委托回调实例方法

请看看InstanceDelegateDemo()

委托链

上例运行结果:
image.png
来自《CLR via C# 第4版》:
image.png
所以当我执行了6次方法,运算结果还是6。

委托协变性和逆变性

协变性是指方法能返回从委托的返回类型派生的一个类型。
逆变性是指方法获取的参数可以是委托的参数类型的基类。

例如下面这个委托:

delegate Object MyCallback(FileStream s);

完全可以构造该委托类型的一个实例并绑定具有以下原型的方法:

String SomeMethod(Stream s);

其中返回类型(String)派生自委托的返回类型(Object)。
参数类型(Stream)是委托的参数类型(FileStream)的基类。

用委托回调多个方法(委托链)

请查看ChainDelegateDemo1();委托链是委托对象的集合。fb(val)等价于fb.Invoke(val),Invoke方法会执行一个循环来遍历数组中的所有元素,并依次调用每个委托包装的方法。

委托的局限性

在委托链中,Invoke方法包含了对数组中的所有项进行遍历的代码。但除了最后一个返回值,其他所有回调方法的返回值都会被丢弃。如果被调用的委托中有一个抛出了异常或阻塞了相当长一段时间,则链中后续的所有对象都调用不了。

取得对委托链调用的更多控制

为了解决这个算法有时候不胜其任,可以用GetInvocationList实例方法,用于显式调用链中的每一个委托,并允许你使用需要的任何算法。

可以很容易地写一个算法来显式调用数组中每个对象。以下代码进行了演示:

 internal sealed class Light{
        public String SwitchPosition() {
            return "The light is off";
        }
    }

    internal sealed class Fan {
        public String Speed() {
            throw new InvalidOperationException("The fan broke due to overheating");
        }
    }

    internal sealed class Speaker {
        public String Volume() {
            return "The volume is loud";
        }
    }
    
     class Program
    {
        //定义委托来查询一个组件的状态
        private delegate String GetStatus();
        static void Main(string[] args)
        {
            GetStatus getStatus = null;

            getStatus += new GetStatus(new Light().SwitchPosition);
            getStatus += new GetStatus(new Fan().Speed);
            getStatus += new GetStatus(new Speaker().Volume);

            //显示整理好的状态报告,反映这3个组件的状态
            Console.WriteLine(GetComponentStatusReport(getStatus));

            Console.ReadLine();
        }

        private static String GetComponentStatusReport(GetStatus status) {
            if(status == null) { return null; }

            StringBuilder report = new StringBuilder();
            //获得一个数组,其中每个元素都是链中的委托
            Delegate[] delegates = status.GetInvocationList ();

            //显示的遍历数组中的每一个委托
            foreach (GetStatus getStatus in delegates) {
                try {
                    report.AppendFormat("{0}{1}{1}", getStatus.Invoke(),Environment.NewLine);
                } catch (Exception e) {
                    Object component = getStatus.Target;
                    report.AppendFormat("Failed to get status from {1}{2}{0} Error:{3}{0}{0}",
                        Environment.NewLine,
                        ((component == null)? "" : component.GetType() + "."),
                        getStatus.Method.Name,e.Message);
                }
            }
            return report.ToString();
        }
    }

运行代码,结果输出:
image.png

委托定义不要太多(泛型委托)

.NET Framework提供了17个Action委托:

public delegate void Action();
public delegate void Action<T>(T obj);
public delegate void Action<T1, T2>(T1 arg1, T2 arg2);
......

还提供了17个Func函数,允许回调返回返回值:

public delegate TResult Func<TResult>();
public delegate TResult Func<T, TResult>(T arg);
......

建议尽量使用这些委托类型,而不是在代码中定义更多的委托类型。这样可减少系统中的类型数量,同时简化代码。
然而,如需使用ref或out关键字以传引用的方法传递参数,就可能不得不定义自己的委托:

delegate void Bar(ref int z);

WinRT
24 声望4 粉丝

临渊羡鱼,不如退而结网