C#中的委托

什么是委托

  1. 委托是持有一个或多个方法的对象,执行委托类型的对象执行它“持有”的方法
  2. 如果我们要把方法当做参数来传递的话,就要用到委托。简单来说委托是一个类型,这个类型可以赋值一个方法的引用
  3. c#的委托可以看成c++的一个类型安全的、面向对象的c++函数指针

怎么创建委托

  1. 用关键字delegate,声明委托类型,返回值类型,方法的参数类型(类似我们声明一个类)
  2. 用已经声明的委托类型声明一个变量,变量持有对委托对象的引用
  3. 把同返回值和参数类型的方法,赋值给委托类型的变量,该变量持有对委托对象的引用
internal class Program
{
    delegate void IntMethodInvoker(int i);
    delegate void TwoLong(long a, long b);
    delegate string GetAString();
    static void Main(string[] args)
    {
        IntMethodInvoker intvoker = null;
        TwoLong twoLong = null;
        //委托类型的引用赋值
        intvoker = test;    //类内静态方法直接使用
        intvoker(10);
        //调用委托时要判空
        if(twoLong != null)
        {
            twoLong(2, 23);
        }
        int x = 123;
        GetAString getAString = x.ToString;
        GetAString getAString1 = new GetAString(x.ToString);
        Console.WriteLine(getAString);
        
        
    }

    private static void test(int i)
    {
        Console.WriteLine("我是哈哈哈"+i);
    }
}

委托与类的对比

委托
声明类型声明类声明委托(类型)
声明类型声明类类型的变量声明委托类型的变量
实例化创建类的实例并把它的引用赋值给变量创建委托的实例并把它的引用赋值给变量,然后增加第一个方法
使用变量使用类对象调用委托对象

进一步理解委托

  1. 委托持有的方法可以是任何类或结构的方法,可以是静态方法,实例化对象的方法
  2. 委托的参数类型可以包括ref和out修饰符
  3. 委托持有方法的列表称为调用列表
  4. 调用委托就会执行调用列表的所有方法

拆解

声明委托类型

delegate void Mydel(int x);
  1. 委托类型为Mydel,可以被委托的方法返回值必须是void,参数类型必须是int
  2. 与方法声明不同,在 C# 中,方法声明必须在类或结构体内部,而委托类型声明不需要在类内部声明

创建委托对象

1. 通过new创建对象

MyDel delVar;
delVar = new MyDel(myInstObj.MyM1);

2.可以通过方法名称和委托类型之间的隐式转换

delVar = myInstObj.MyM1;
  1. 委托是引用类型,所以要有引用和对象
  2. 赋值给委托类型的引用的方法,返回值和签名必须与声明委托类型时候保持一致
  3. 创建委托对象时候,会把第一个方法放入委托的调用列表
  4. 委托的实例本身是引用类型,因此委托实例存储在堆上。而委托所引用的方法则可能存储在堆上或者栈上,具体取决于方法的生存期。
  5. 委托的实例是一个引用类型的实例(而不是值类型),那么该方法会存储在堆上,引用的地址是堆里的地址
  6. 如果委托引用的方法是一个静态方法,或者是一个实例方法,但委托的实例是一个值类型的实例,那么该方法可能会存储在栈上。这是因为值类型的实例通常存储在栈上,而静态方法则不需要与特定的对象实例关联。

组合委托

  1. 委托是持有一个或多个方法的引用
  2. 可以给委托添加方法+=,也可以移除方法-=

为委托添加方法

MyDel delVar = inst.MyM1;
delVar += SC1.m3;
delVar += X.Act;

从委托移除方法

delVar  -= SC1.m3;
  1. -=会从列表最后开始查找,移除第一个与方法匹配的实例

委托是恒定的

  1. 委托和字符串类似,都是不可变的,委托对象被创建之后不能再被改变
  2. 在添加方法时候,实际会在堆里新创建一个新委托,把新委托赋值给引用变量,这样旧委托没有被引用,等待GC回收内存
  3. 同理,移除也是创建新委托

调用委托

1. 与调用方法一样调用委托

MyDel delVar = inst.MyM1
delVar += SC1.m3;
delVar += X.Act;
if(delVar!=null){
	delVar(55);
}
  1. 调用空委托会抛出异常,所以在调用委托时候,要先判断委托的调用列表是否为空,调用列表为空,则委托类型的引用==null
  2. 调用委托后,会调用调用列表的所有方法,先添加的方法先调用
  3. 会把55参数会作用于调用列表的每一个方法(除非一个参数是输出参数)

2. 使用Invoke和空条件运算符调用

delVar?.Invoke(65);

示例


namespace Test
{
    //可以在类外声明委托
    delegate void PrintFuction();

    class Test
    {
        public void Print1()
        {
            Console.WriteLine("我是Print1");
        }
        public static void Print2()
        {
            Console.WriteLine("我是Print2");
        }
    }
    internal class Program
    {
        static void Main(string[] args)
        {
            Test test = new Test();
            //声明委托类型引用
            PrintFuction pf;
            //实例化并初始化委托,等价于pf = new PrintFuntion(test.Print1);
            pf = test.Print1;

            //添加方法,必须返回值和签名都一致
            pf += Test.Print2;//静态方法
            pf += test.Print1;
            pf += Test.Print2;

            //调用委托
            if(pf != null)
            {
                pf();
            }
            else
            {
                Console.WriteLine("委托调用列表为空");
            }
			pf?.Invoke();
        }
    }
}

调用带有返回值的委托

  1. 调用列表的最后一个方法的返回值就是委托调用返回的值

调用带有引用参数的委托

  1. 参数值会根据调用列表的一个或多个方法的返回值而改变
  2. 比如第一个被调用的方法改变引用参数的值,那第二个方法传进去的参数虽然还是那个引用,但是对应的值已经在第一个方法中改变了
示例

namespace Test
{

    delegate void MyDel(ref int x);

    
    internal class Program
    {
        public void Add2(ref int x)
        {
            x += 2;
        }
        public void Add3(ref int x)
        {
            x += 3;
        }
        static void Main(string[] args)
        {

            Program program = new Program();
            MyDel mDel = program.Add2;
            mDel += program.Add3;
            mDel += program.Add2;

            int x = 5;
            //调用委托,参数为引用参数
            mDel(ref x);

            Console.WriteLine(x);
            //打印出12

        }
    }
}

内置的委托

Action委托

  1. 返回值为void,参数为0个或多个任意类型参数
  2. 用泛型去设置Action参数
internal class Program
{
    private static void  Test1()
    {
        Console.WriteLine("test1");
    }
    private static void Test2(int x)
    {
        Console.WriteLine("test2"+x);
    }
    private static void Test3(double x, int y)
    {
        Console.WriteLine("test3" + x + y);
    }
    static void Main(string[] args)
    {
        Action action = Test1;
        action();

        Action<int> method = Test2;
        method(124);

        Action<double,int> method2 = Test3;
        Test3(1, 2);
    }
}

Func委托

  1. 要带有返回值和参数的委托
  2. 泛型先设置参数类型
  3. 返回值类型在泛型最后设置
internal class Program
{
    private static string Test1()
    {
        return "ssss";
    }
    private static string Test2(int x, double y)
    {
        return "sss" + x + y;
    }
    static void Main(string[] args)
    {
        Func<string> f = Test1;
        Console.WriteLine(f);

        Func<int,double,string> f2 = Test2;
        Console.WriteLine(f2(1,2));
    }
}

应用

委托类型作为参数传递

  1. 因为委托类型的变量可以接受方法名字作为参数
  2. 实现传递方法的作用
delegate double DoubleOpDelegate(double x);
static void ProcessAndDisplayRes(DoubleOpDelegate op, double value)
{
    double result = op(value);
    Console.WriteLine(result);

}

委托类型数组

  1. 委托类型跟类一样,都是数据类型,所以也可以有数组

MathOp类

internal class MathOp
{
    public static double MultiplayByTwo(double value)
    {
        return value * 2;
    }
    public static double Square(double value)
    {
        return value * value;
    }
}

测试

internal class Program
{
    
    delegate double DoubleOpDelegate(double x);
    static void Main(string[] args)
    {
        //定义一个委托类型的数组,并且赋值
        DoubleOpDelegate[] operations = { MathOp.MultiplayByTwo, MathOp.Square };
        foreach (DoubleOpDelegate operation in operations)
        {
            Console.WriteLine(operation(3));

        }
    
    }

}

用委托升级冒泡排序

普通冒泡排序

internal class Program
{
    private static void Sort(int[] sortArray)
    {
        bool swapped = true;
        do
        {
            swapped = false;
            for (int i = 0;i < sortArray.Length-1; i++)
            {
                if (sortArray[i] > sortArray[i + 1])
                {
                    int temp = sortArray[i];
                    sortArray[i] = sortArray[i + 1];
                    sortArray[i + 1] = temp;
                    swapped = true;
                }
            }
        } while (swapped);
    }
    static void Main(string[] args)
    {
        int[] s = { 1, 2, 6, 4, 52, 5, 6, 7, 23 };
        Sort(s);
        foreach (int i in s)
        {
            Console.WriteLine(i);
        }
    }
}

但是当我们需要排序一个Employee雇员类的数组时,比如想按照雇员工资排序,雇员年龄排序等等,我们应该怎么排序,或者现在想要排序Student类的数组呢,逻辑一样但是要重构排序方法,怎么解决

按照工资排序

雇员类

internal class Employee
{
    public string Name {  get; private set; }
    public double Salary {  get; private set; }

    public Employee(string name, double salary)
    {
        Name = name;
        Salary = salary;
    }
    public static bool CompareSalary(Employee a, Employee b)
    {
        return a.Salary > b.Salary;
    }
}

在类里声明了比较两个雇员工资的方法

使用Func委托传递方法的冒泡排序

public static void Sort<T>(T[] array, Func<T, T, bool> compare)
{
    bool swapped = true;
    do
    {
        swapped = false;
        for (int i = 0; i < array.Length - 1; i++)
        {
            if (compare(array[i], array[i+1]))
            {
                T temp = array[i];
                array[i] = array[i + 1];
                array[i + 1] = temp;
                swapped = true;
            }
        }
    } while (swapped);
}

通过泛型解决了排序对象的类型问题,可以排序各种类型的数组,比如Student类等等,再通过Func委托传递比较大小方法,解决了按照什么数据成员排序的问题,比如可以按照年龄排序,身高排序,工资排序,只需要传入对应的比较两个数大小方法

测试

static void Main(string[] args)
{
    Employee[] employees =
    {
        new Employee("Bunny",20000),
        new Employee("Mich",10000),
        new Employee("si",2000),
        new Employee("cc",1000),
        new Employee("Bunn",30000),
        new Employee("Bunny",20000),

    };

    Sort<Employee>(employees, Employee.CompareSalary);
    foreach (Employee employee in employees)
    {
            Console.WriteLine(employee.Name+":"+employee.Salary);
    }
    
}
  • 19
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
C#委托是一种类型,它可以用来引用一个或多个方法,并允许将方法作为参数传递给其他方法。委托的具体使用如下: 1. 定义委托类型:首先需要定义一个委托类型,可以使用`delegate`关键字来定义。例如,定义一个委托类型`MyDelegate`: ```csharp delegate void MyDelegate(string message); ``` 2. 创建委托实例:可以使用委托类型来创建委托实例,将其与一个或多个方法关联起来。例如,创建一个委托实例`myDelegate`并关联一个方法`PrintMessage`: ```csharp void PrintMessage(string message) { Console.WriteLine(message); } MyDelegate myDelegate = new MyDelegate(PrintMessage);``` 3. 调用委托:通过委托实例可以调用关联的方法。例如,通过`myDelegate`调用关联的方法`PrintMessage`: ```csharp myDelegate("Hello, World!"); ``` 4. 多播委托委托还支持多播(多个方法)调用。可以使用`+=`和`-=`操作符来添加和移除方法。例如,添加一个方法`PrintAnotherMessage`到委托实例`myDelegate`: ```csharp void PrintAnotherMessage(string message) { Console.WriteLine("Another message: " + message); } myDelegate += PrintAnotherMessage; ``` 5. 委托作为参数:委托可以作为方法的参数传递。例如,定义一个方法`ProcessMessage`,接受一个委托类型的参数: ```csharp void ProcessMessage(MyDelegate delegateInstance, string message) { delegateInstance(message); } ProcessMessage(myDelegate, "Hello, C#!"); ``` 6. 匿名方法和Lambda表达式:除了使用命名方法,还可以使用匿名方法和Lambda表达式来创建委托实例。例如,使用Lambda表达式创建一个委托实例: ```csharp MyDelegate myDelegate = (message) => Console.WriteLine(message); ```
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值