C# 委托

目录

1.委托是什么?

2.委托的作用?

3.委托的内部构造

4.如何使用委托?


1.委托是什么?

微软官方文档对其定义个人认为还是比较准确的:委托是一种引用类型,表示对具有特定参数列表返回类型方法的引用。在实例化委托时,你可以将其实例与任何具有兼容签名和返回类型的方法相关联,然后可以通过委托实例调用该方法

概念还是有点抽象,通过简单的代码对概念进行解构就清晰多了:

①创建委托:

//这个委托的特征是:无返回值,一个string形参
public delegate void SaySomething(string value);

②定义一个无返回值、一个string形参的方法:

public static void TellTime(string time)
{
    Console.WriteLine($"报时:现在是:{time}");
}

根据概念可知,方法的返回类型和签名必须完全与定义的委托相同,这是委托一个方法的关键因素。

③将方法传递给委托:

SaySomething say = TellTime;

注意写法:SaySomething代表定义的委托名称,say是委托实例,TellTime是要引用的方法。

④将委托作为实参传递给一个方法:

//这个方法的动作是当摆钟到整点时,需要做整点报时
public static void DoSomething(SaySomething say)
{
    Console.WriteLine("动作:大摆正分钟摆动到12点");
    Console.WriteLine("提醒:接下来,即将整点报时");
    say("12");
}

⑤执行方法,查看结果:

可以看到,方法在合适的时间执行了委托传递的方法。

2.委托的作用?

对方法调用的解耦:有了委托之后,就可以把方法的引用封装在委托对象中,然后将这个委托对象传递给一个方法,该方法内部便使用的是委托对象而不是直接调用某一个方法,所以在编译阶段,方法内部是不会知道具体调用了哪一个方法的,只有在运行时,当给委托对象委托了一个方法之后,方法在执行时才确定的会调用某一个(多个)方法。

来看一个使用委托解耦的程序:

不使用委托时:

public static string GetDataByMysql(int id)
{
    //模拟查询过程
    //Mysql过于复杂的查询算法
    if ((id * 2 / 2) == 1)
    {
        return "小张的员工数据";
    }
    if ((id * 2 / 2) == 2)
    {
        return "小王的员工数据";
    }
    return "未查询到员工数据";
}
    
public static void GetEmployeeData()
{
    Random r = new Random();
    int employeeId = r.Next(0, 3);
    string employeeData = GetDataByMysql(employeeId);
    Console.WriteLine(employeeData);
}

当上面的代码一写完,你发现SQL Server性能更高一点,这时你想切换SQL Server来查询员工数据:

public static string GetDataBySqlServer(int id)
{
    //模拟查询过程
    if (id == 1)
    {
        return "小张的员工数据";
    }
    if (id == 2)
    {
        return "小王的员工数据";
    }
    return "未查询到员工数据";
}

public static void GetEmployeeData()
{
    Random r = new Random();
    int employeeId = r.Next(0, 3);
    string employeeData = GetDataBySqlServer(employeeId);
    Console.WriteLine(employeeData);
}

除了增加了使用Sqlserver查询数据的代码之外,最主要的就是在GetEmployeeData方法中使用GetDataBySqlServer的方法签名替换了GetDataByMySql的方法签名,注意,这样的做法导致了GetEmployeeData()方法的内部变动,想想当这样的方法调用遍布在程序的各个位置,然后程序还经常重构的话,那么就真的知道什么是牵一发而动全身了。

接下来使用委托解决以上问题:

第一次使用Mysql查数据:

public static string GetDataByMysql(int id)
{
     ...
}
public static void GetEmployeeData(EmployeeData data)
{
    Random r = new Random();
    int employeeId = r.Next(0, 3);
    string employeeData = data(employeeId);
    Console.WriteLine(employeeData);
}
public static void Main(string[] arg)
{
    EmployeeData data = GetDataByMysql;
    GetEmployeeData(data);
    Console.ReadLine();
}

然后重构为使用Sql server查数据:

public static string GetDataBySqlServer(int id)
{
    ...
}
public static void GetEmployeeData(EmployeeData data)
{
    Random r = new Random();
    int employeeId = r.Next(0, 3);
    string employeeData = data(employeeId);
    Console.WriteLine(employeeData);
}
public static void Main(string[] arg)
{
    EmployeeData data = GetDataBySqlServer;
    GetEmployeeData(data);
    Console.ReadLine();
}

可以看到,程序现在可以使用Sqlserver的方式查询数据,但GetEmployeeData方法一点都没变动,所以程序的变化对GetEmployeeData方法在代码层面没有一点影响。这就体现了使用委托对程序做解耦的优势了!

3.委托的内部构造

表面上,我们使用delegate就可以定义一个委托,但其实委托的内部构造还是依托于类。那么通过反编译工具来看一下一个委托的内部构造:

//定义一个委托
public delegate object FeedBack(Int32 value);

//编译器和CLR协同实现的委托内部
public class FeedBack:System.MulticastDelegate {
    public FeedBack(Object @object,IntPtr method);
    public virtual void Invoke(Int32 value);
    public birtual IAsyncResult BeginInvoke(Int32 value,AsyncCallback callback,Object @object);
    public virtual void EndInvoke(IAsyncResult result);
}

对于构造器

  1. Object:
    1. 实例方法会保存对象引用
    2. 静态方法是null
  2. IntPtr: 标识要回调的方法

所以每个委托对象实际是一个包装器,其中包装了一个方法和调用该方法时要操作的对象。

4.如何使用委托?

哪些地方可以定义委托:委托既可以在一个类型中定义,也可在全局范围中定义。简单的说,由于委托是类,所以凡是能够定义类的地方,都能定义委托。

4.1 用委托回调静态方法

调用静态方式的例子:

public class Program
{
    public delegate void FeedBack(int value);

    private static void FeedBackToConsole(int value)
    {
        Console.WriteLine($"value is {value}");
    }

    public static void Main(string[] arg)
    {
        FeedBack feedBack = FeedBackToConsole;
        TestDelegate.Out(feedBack);
        Console.ReadLine();
    }
}
class TestDelegate
{
    public static void Out(FeedBack feedBack)
    {
        feedBack?.Invoke(1);
    }
}

这个例子是为了说明:在一个类型中通过委托来调用另一个类型的私有成员,只要委托对象是由具有足够安全/可访问性的代码创建的,便没有问题

委托的协变性与逆变性:将方法绑定到委托时,C#和CLR都允许引用类型的协变性和逆变性。协变性是指方法能返回从委托的返回类型派生的一个类型。逆变性是指方法获取的参数可以是委托的参数类型的基类。

例子:

delegate Object MyCallback(FileStream s);
String SomeMethod(Stream);

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

委托链是委托对象的集合,可利用委托链调用集合中的委托所代表的全部方法。

通过了解委托的内部构造可知,定义的委托都会继承MulticastDelegate类,而该类中有一个_invocationList字段:

该字段存储了所有的委托对象,所以该字段就相当于是委托链,然后在执行委托对象(调用Invoke方法)时,就会调用该字段的每一个委托方法。

伪码:

public void Invoke(Int32 value){
    Delegate[] delegateSet = _InvocationList as Delegate[];
    foreach(Feedback d in delegateSet)
    {
        d(value);
    }else{
        _methodPtr.Invoke(_target,value);
    }
}

委托链的例子:

public class Program
{
    public delegate void FeedBack(Int32 value);

    private static void  FeedBackToConsole(Int32 value)
    {
        Console.WriteLine($"FeedBackToConsole is {value}");
    }

    public static void FeedBackToOthers(Int32 value)
    {
        Console.WriteLine($"FeedBackToOthers is {value}"); 
    }

    public static void Main(string[] arg)
    {
        //FeedBack feedBack = new FeedBack(FeedBackToConsole);同下
        FeedBack feedBack = FeedBackToConsole;
        //FeedBack feedBack1 = FeedBackToOthers;
        //feedBack = (FeedBack)Delegate.Combine(feedBack, feedBack1);同下
        feedBack += FeedBackToOthers;
        TestDelegate.Out(feedBack);
        Console.WriteLine("------------------------");
        //feedBack = (FeedBack)Delegate.Remove(feedBack, feedBack1);同下
        feedBack -= FeedBackToConsole;
        TestDelegate.Out(feedBack);
        Console.ReadLine();
    }
}

class TestDelegate
{
    public static void Out(FeedBack feedBack)
    {
        feedBack?.Invoke(1);
    }
}

结果:

需要注意: +=和-=这两个操作符是被重载过的,+=相当于Combine函数,-=相当于Remove函数。

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

4.3.1 不需要构造委托对象:

private static void  FeedBackToConsole(Int32 value)
{
    tonsole.WriteLine($"FeedBackToConsole is {value}"); 
}

//直接传入与Out方法要求的委托对象行为一致的方法
TestDelegate.Out(FeedBackToConsole);

public delegate void FeedBack(Int32 value);

public static void Out(FeedBack feedBack)
{
     feedBack?.Invoke(1);
}

这是因为C#编译器能自己推断,所以可以省略构造FeedBack委托对象的代码,使代码可读性更佳,也更容易理解

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值