《CLR via C#》基本类型.委托

17.委托

.NET框架通过委托来提供回调函数机制,委托能确保回调函数是类型安全的(即方法的签名相同)。登记回调方法来获得通知。

17.1初识委托

internal sealed class DelegateIntroduce
{
    internal delegate void Feedback(int value); // 委托类型Feedback的声明,可以把Feedback看成一个类型

    public static void Go()
    {
        StaticDelegateDemo();
        InstanceDelegateDemo();
        ChainDelegateDemoBad(new DelegateIntroduce());
        ChainDelegateDemoGood(new DelegateIntroduce());
    }
    
    private static void StaticDelegateDemo() // 静态委托
    {
        Console.WriteLine("----静态委托举例----");
        Counter(1, 2, new Feedback(WriteToConsole)); // 登记回调方法,第三个参数是新构造的Feedback委托对象
        Counter(1, 2, new Feedback(WriteToFile)); // 委托对象是方法的包装器,WriteToFile被传给委托类型Feedback的构造器
        Console.WriteLine();
    }

    private static void InstanceDelegateDemo() // 实例委托
    {
        Console.WriteLine("----实例委托举例----");
        DelegateIntroduce di = new DelegateIntroduce();
        Counter(1, 2, new Feedback(di.WriteToMsgBox));
        Console.WriteLine();
    }
    
    private static void ChainDelegateDemoBad(DelegateIntroduce di)
    {
        Console.WriteLine("----ChainDelegateDemoBad-----");
        Feedback fb1 = new Feedback(WriteToConsole);
        Feedback fb2 = new Feedback(di.WriteToMsgBox);

        Feedback fbChain = null;
        fbChain = (Feedback)Delegate.Combine(fbChain, fb1);
        fbChain = (Feedback)Delegate.Combine(fbChain, fb2);
        Counter(1, 2, fbChain);

        fbChain = (Feedback)Delegate.Remove(fbChain, fb1); // 移除一个委托,Remove是新建一个委托对象,所以务必进行赋值
        Counter(1, 2, fbChain);
        Console.WriteLine();
    }

    private static void ChainDelegateDemoGood(DelegateIntroduce di)
    {
        Console.WriteLine("----ChainDelegateDemoGood----");
        Feedback fb1 = new Feedback(WriteToConsole);
        Feedback fb2 = new Feedback(di.WriteToMsgBox);

        Feedback fbChain = null;
        fbChain += fb1; // C#编译器为委托类型的实例重载了+=和-=操作符,这些操作符分别调用Combine和Remove
        fbChain += fb2;
        Counter(1, 2, fbChain);

        fbChain -= fb1;
        Counter(1, 2, fbChain);
        Console.WriteLine();
    }

    private static void Counter(int from, int to, Feedback fb)
    {
        for (int i = from; i <= to; ++i)
        {
            if (null != fb)
                fb(i);
        }
    }

    private static void WriteToConsole(int value)
    {
        Console.WriteLine("item = " + value);
    }

    private static void WriteToFile(int value)
    {
        StreamWriter sw = new StreamWriter("result.text", true); // true表示append,result.text文件与.exe同目录
        sw.WriteLine("item = " + value);
        sw.Close();
    }

    private void WriteToMsgBox(int value)
    {
        MessageBox.Show("item = " + value);
    }
}

17.2用委托回调静态方法

如上所述。

17.3用委托回调实例方法

如上所述。另外,包装实例方法很有用,因为对象可以维护一些状态,并可以在回调方法执行期间利用这些状态信息

17.4委托揭秘

本节介绍C#编译器和CLR如何协同工作来实现委托。
重新审视这行代码internal delegate void Feedback(int value);,编译器实际会定义一个完整的类型,该类型里有构造器public Feedback(Object @object, intPtr method);、一个签名与这行代码相同的虚方法Invoke(该节讨论)、异步回调的两个方法(第27章讨论)。用ILDasm查看生成的程序集,编译器的确生成了Feedback类。
在这里插入图片描述
由于委托是类,所以凡是能定义类的地方(嵌套在一个类型里或者在全局范围内),就能定义委托。

所有委托类型都派生自MulticastDelegate,所以它们都继承了MulticastDelegate的字段、属性和方法,其中有三个非公共字段(注意:在MulticastDelegate上按F12看不到非公共字段)最重要:
1._target,类型为System.Object,该字段引用的是回调方法要操作的对象
2._methodPtr,类型为System.IntPtr,IntPtr为指针的平台特定类型,一个内部的整数值,该字段标识要回调的方法
3._invocationList,类型为System.Object,该字段常为null,它引用一个委托数组

构造器的两个实参分别保存在_target和_methodPtr私有字段中,如下图:
在这里插入图片描述
了解委托对象的内部结构后,再来看看回调方法是如何调用的:
举例fb(val)
编译器知道Feedback的实例fb是一个引用了委托对象的变量(如上图的一个圆角方框就是一个委托对象),所以会生成代码调用该委托对象的Invoke方法。所以,fb(val)等价于fb.Invoke(val)。所以,if (null != fb) fb(i);可以简写为fb?.Invoke(i);

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

internal static class GetInvokeList // 得到调用列表(全部以实例方法为例)
{
    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.";
        }
    }

    internal delegate string GetStatus(); // 声明一个委托

    public static void Go()
    {
        GetStatus getStatus = null; // 声明一个委托链
        getStatus += new GetStatus(new Fan().Speed);
        getStatus += new GetStatus(new Speaker().Volume);
        Console.WriteLine(GetComponentStatusReport(getStatus));
    }

    private static string GetComponentStatusReport(GetStatus getStatus)
    {
        if (null == getStatus) return "";
        Delegate[] delegatesArray = getStatus.GetInvocationList();
        StringBuilder report = new StringBuilder();
        foreach (GetStatus status in delegatesArray)
        {
            try
            {
                report.AppendFormat("{0}{1}{1}", status(), Environment.NewLine);
            }
            catch (InvalidOperationException e) 
            {
                Object component = status.Target; // status是委托,status.Target引用的是回调方法要操作的对象
                report.AppendFormat("Failed to get status from {1}{2}{0}   Error: {3}{0}", Environment.NewLine, 
                    (component == null) ? "" :component.GetType() + ".", status.GetMethodInfo().Name, 
                    e.Message); // status.GetMethodInfo()返回指定委托所表示的方法
            }
        }
        return report.ToString();
    }
}

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

.NET框架提供了17个Action委托供用户选择,从0个类型参数到最多16个类型参数;还提供了17个Func委托以允许回调方法返回值。

17.7C#为委托提供的简化语法

1.可以不定义回调方法(用lambda表达式实现匿名方法)

lambda表达式操作符是=>,编译器看到=>就会自动定义一个新的私有方法,该方法称为匿名函数。=>左侧是参数名称;=>右侧是函数主体,被放入编译器生成的匿名函数中。
=>左侧的规定:

Func<String> f1 = () => "jump"; // 委托不获取任何参数时
Func<int, string> f2 = n => n.ToString(); // 委托是1个参数时,可以不加参数的括号

// 两个int是方法的输入类型,一个string是方法的输出类型
Func<int, int, string> f3 = (int n1, int n2) => (n1 + n2).ToString(); // 委托获取1个或多个参数时,可显示指定类型
Func<int, int, string> f4 = (n1, n2) => (n1 + n2).ToString(); // 委托获取1个或多个参数时,编译器也可推断类型

=>右侧的规定:
主体只有一个语句就可以不加大括号,否则就要加。如果委托还期待返回值,还得在主体中加return语句,且必须加大括号。

2.不需要构造委托对象

lambda表达式 替代 委托对象 举例:

String[] names = { "Jeff", "Kristin", "Aidan" };
Char charToFind = 'i';
names = Array.FindAll(names, (String name) => { return (name.IndexOf(charToFind) >= 0); });
names = Array.ConvertAll<String, String>(names, (String name) => { return name.ToUpper(); });
Array.Sort(names, (String name1, String name2) => { return String.Compare(name1, name2); });
Array.ForEach(names, (String name) => { Console.WriteLine(name); });
输出:
AIDAN
KRISTIN

补充:不是所有匿名函数的的表达方式都叫做lambda表达式,Array.ForEach(names, delegate (String name) { Console.WriteLine(name); });这个写法也叫匿名方法,在C# 2.0中引入,现在已经被lambda表达式这种写法淘汰。

17.8委托和反射

internal delegate Object TwoInt(int num1, int num2);
internal delegate Object OneStr(string str);

internal static class DelegateReflectionTest
{
    public static void Go(params string[] args) // 用params实现数量可变的参数
    {
        if (args.Length <= 2)
        {
            Console.WriteLine("error:args count is wrong.");
            return;
        }

        Type delegateType = Type.GetType(args[0]); // 第一个参数是委托类型
        if (null == delegateType)
        {
            Console.WriteLine("Invalid delegateType arg: " + args[0]);
            return;
        }
        
        Delegate myDelegate;
        try
        {
            // 第二个参数是方法名,反射,Type.GetTypeInfo()返回表示类类型的类型声明,mi是引用了回调方法的MethodInfo对象
            // GetDeclaredMethod:获取类中声明的所有方法(从父类继承的不算),包括public、protected和private修饰的方法
            // GetMethod:获取当前类和父类的所有public的方法
            MethodInfo methodInfo = typeof(DelegateReflectionTest).GetTypeInfo().GetDeclaredMethod(args[1]);
            // 编译时,在不知道委托的所有必要信息的前提下,根据第一个参数(委托类型)来创建委托对象
            myDelegate = methodInfo.CreateDelegate(delegateType);
        }
        // ArgumentNullException来自于GetDeclaredMethod方法,而ArgumentNullException是ArgumentException的子类
        catch (ArgumentException) 
        {
            Console.WriteLine("Invalid methodName argument: " + args[1]);
            return;
        }

        // 要传给方法的参数
        Object[] callbackArgs = new Object[args.Length - 2];

        if (myDelegate.GetType() == typeof(TwoInt))
        {
            try
            {
                for (int i = 2; i < args.Length; ++i)
                    callbackArgs[i - 2] = int.Parse(args[i]); // 字符串转化为整数
            }
            catch (FormatException) 
            {
                Console.WriteLine("Params must be int.");
                return;
            }
        }
        if (myDelegate.GetType() == typeof(OneStr))
        {
            Array.Copy(args, 2, callbackArgs, 0, callbackArgs.Length);
        }

        try
        {
            Object result = myDelegate.DynamicInvoke(callbackArgs);
            Console.WriteLine("Resule = " + result);
        }
        catch(ArgumentException)
        {
            Console.WriteLine("Incorrect number of parameters specified.");
            return;
        } 
    }

    private static Object Add(int num1,int num2)
    {
        return num1 + num2;
    }
    private static Object CharsLength(String s1)
    {
        return s1.Length;
    }
}
输出:
error:args count is wrong.
Resule = 444
Resule = 11

猜你喜欢

转载自blog.csdn.net/BillCYJ/article/details/92089702