C# - 委托 Delegates

如果我们想传递一个函数作为参数怎么办? C# 如何处理回调函数或事件处理程序? 答案是——委托。

委托是定义方法签名的引用类型数据类型。 您可以定义委托的变量,就像其他数据类型一样,可以引用与委托具有相同签名的任何方法。

我们使用委托时会涉及到三个步骤:

  • 声明一个委托 Delegate
  • 设置目标方法
  • 调用委托 Delegate

可以使用 delegate 关键字后跟函数签名来声明委托,如下所示。

[访问修饰符] delegate [返回类型] [委托名称]([参数...])

下面声明了一个名为 MyDelegate 的委托。

public delegate void MyDelegate(string msg);

上面,我们已经声明了一个具有 void 返回类型和字符串参数的委托 MyDelegate。 委托可以在类外或类内声明。 推荐 声明在类之外。

声明委托后,我们需要设置目标方法或 lambda 表达式。 我们可以通过使用 new 关键字创建委托对象并传递签名与委托签名匹配的方法来实现。

public delegate void MyDelegate(string msg); // 声明一个委托
class Program
{
    // 跟MyDelegate有相同参数和返回值的方法 称为签名相同
    static void MethodA(string message)
    {
        Console.WriteLine(message);
    }

    static void Main(string[] args)
    {
        // 把函数传给委托
        MyDelegate del1 = new MyDelegate(MethodA);
        // 直接赋值给委托
        MyDelegate del2 = MethodA;
        // 使用 lambda表达式
        MyDelegate del3 = (string msg) => Console.WriteLine(msg);

        Console.ReadKey();
    }
}

设置目标方法后,可以使用 Invoke() 方法或使用 () 运算符调用委托。

            del1.Invoke("1");
            del2("2");

完整的代码如下

using System;

namespace ConsoleApp1
{
    public delegate void MyDelegate(string msg); // 声明一个委托
    class Program
    {
        // 跟MyDelegate有相同参数和返回值的方法
        static void MethodA(string message)
        {
            Console.WriteLine(message);
        }

        static void Main(string[] args)
        {
            // 把函数传给委托
            MyDelegate del1 = new MyDelegate(MethodA);
            // 直接赋值给委托
            MyDelegate del2 = MethodA;
            // 使用 lambda表达式
            MyDelegate del3 = (string msg) => Console.WriteLine(msg);

            del1.Invoke("1");
            del2("2");
            del3("3");

            Console.ReadKey();
        }
    }
}

输出

1
2
3

将委托作为参数传递

一个方法可以有一个委托类型的参数,如下所示。

using System;

namespace ConsoleApp1
{
    public delegate void MyDelegate(string msg); // 声明一个委托
    class Program
    {
        static void Main(string[] args)
        {
            MyDelegate del = ClassA.MethodA;
            InvokeDelegate(del);

            del = ClassB.MethodB;
            InvokeDelegate(del);

            del = (string msg) => Console.WriteLine("调用 lambda 表达式: " + msg);
            InvokeDelegate(del);
        }

        static void InvokeDelegate(MyDelegate del) // 参数是一个委托
        {
            del("Hello World");
        }
    }

    public class ClassA
    {
        public static void MethodA(string message)
        {
            Console.WriteLine("调用 ClassA.MethodA(): " + message);
        }
    }

    public class ClassB
    {
        public static void MethodB(string message)
        {
            Console.WriteLine("调用 ClassB.MethodB(): " + message);
        }
    }
}

输出

调用 ClassA.MethodA(): Hello World
调用 ClassB.MethodB(): Hello World
调用 lambda 表达式: Hello World

注意有了泛型之后,我们通常是用Action<>和Func<>而不在自已定义委托了。 Action<> 没有返回值 Func是有返回值的。 如下示例。

using System;

namespace ConsoleApp1
{
    class Program
    {
        static void Main(string[] args)
        {
            Action<string> del = ClassA.MethodA;
            InvokeDelegate(del);

            del = (string msg) => Console.WriteLine("调用 lambda 表达式: " + msg);
            InvokeDelegate(del);
        }

        static void InvokeDelegate(Action<string> del) 
        {
            del("Hello World");
        }
    }

    public class ClassA
    {
        public static void MethodA(string message)
        {
            Console.WriteLine("调用 ClassA.MethodA(): " + message);
        }
    }
}

上面的Action实际是定义在了System下面的 代码如下

public delegate void Action<in T>(T obj);

多播委托 Multicast Delegate

委托可以指向多个方法。 指向多个方法的委托称为多播委托。 “+”或“+=”运算符将函数添加到调用列表中,“-”和“-=”运算符将其删除。

using System;

namespace ConsoleApp1
{
    class Program
    {
        static void Main(string[] args)
        {
            Action<string> del = ClassA.MethodA;
            del += ClassB.MethodB; //使用了 += del=del+ClassB.MethodB
            InvokeDelegate(del);
            del -= ClassB.MethodB;
            del("hello world 2");
            Console.ReadLine();
        }

        static void InvokeDelegate(Action<string> del) // 参数是一个委托
        {
            del("Hello World");
        }
    }

    public class ClassA
    {
        public static void MethodA(string message)
        {
            Console.WriteLine("调用 ClassA.MethodA(): " + message);
        }
    }

    public class ClassB
    {
        public static void MethodB(string message)
        {
            Console.WriteLine("调用 ClassB.MethodB(): " + message);
        }
    }
}

加法和减法运算符始终作为赋值的一部分工作: del += ClassB.MethodB; 完全等同于 del = del+ ClassB.MethodB; 减法也一样。

如果delegate 有返回值的话,哪么只会把最后调用的方法的值返回出来。

using System;

namespace ConsoleApp1
{
    class Program
    {
        static void Main(string[] args)
        {
            Func<int> del = ClassA.MethodA;
            del += ClassB.MethodB;

            var result = del(); 
            Console.WriteLine(result); //输出 200
        }


    }

    public class ClassA
    {
        public static int MethodA()
        {
            Console.WriteLine("调用 ClassA.MethodA(): ");
            return 100;
        }
    }

    public class ClassB
    {
        public static int MethodB()
        {
            Console.WriteLine("调用 ClassB.MethodB(): ");
            return 200;
        }
    }
}

全部输出

调用 ClassA.MethodA():
调用 ClassB.MethodB():
200

泛型委托

泛型委托的定义方式与委托相同,但使用泛型类型参数或返回类型。 设置目标方法时必须指定泛型类型。

例如,考虑以下用于 int 和 string 参数的通用委托。

using System;

namespace ConsoleApp1
{
    public delegate T add<T>(T param1, T param2); // 泛型委托

    class Program
    {
        static void Main(string[] args)
        {
            add<int> sum = Sum;
            Console.WriteLine(sum(10, 20)); //输出 30

            add<string> con = Concat;
            Console.WriteLine(con("Hello ", "World!!"));//输出 Hello World!!
        }

        public static int Sum(int val1, int val2)
        {
            return val1 + val2;
        }

        public static string Concat(string str1, string str2)
        {
            return str1 + str2;
        }
    }
}

正常来说我们会用系统定义好的Action<>和Func<>。而不需要自己定义, 下面的章节会介绍到。

委托还用于声明一个事件和一个匿名方法

下一篇:C# func委托
最近更新的
...