C#中的委托,你真的了解吗?

409 阅读4分钟

图片

委托的实例方法和静态方法目标

当一个实例方法被赋值给委托对象的时候,这个委托对象不仅要保留着对方法的引用,还要保留方法所属实例的引用。

System.Delegate 的Target属性就代表着这个实例。

如果引用的是静态方法,那么Target属性值就是null。

需要理解上面的话,我们可以先了解下概念,静态方法和实例方法。

静态方法: 在C#中由关键字static来定义的静态方法可以通过类名来访问而不需要任何实例对象,但是,在静态方法中不能访问类型中任何非静态成员。

实例方法: 需要通过具体的实例对象来调用,就是通常所说的new关键字来实例化,可访问实例对象中的任何成员。

所以,当这两种方法赋值给委托,实例化方法赋值委托的target属性就会记录目标实例的引用。而静态方法由于不需要实例化,则target属性记录则为null。

public delegate void ProgressReporter(int percentComplete);
public class DelegateTest
{
    static void Main()
    {
        Progress pro = new Progress();
        ProgressReporter p = pro.InstanceProgress;
        p(9);
        //True ,委托target属性记录了原方法的引用
        Console.WriteLine(p.Target == pro);
        ////void InstanceProgress(Int32);
        Console.WriteLine(p.Method);
    }
}
public class Progress
{
    public void InstanceProgress (int percentComplete);
    =>Console.WriteLine(percentComplete);
}

泛型委托类型

委托类型可以包含泛型类型参数

 public delegate T Transformer<T>(T arg);
        public class Utils
        {
            public static void Transform<T>(T[] values,Transformer<T> t)
            {
                for (var i = 0; i < values.Length; i++)
                {
                    values[i] = t(values[i]);
                }
            }
        }
        public class Test
        {
           public static void Main()
           {
                int[] values = {1,2,3};
                Utils.Transform(values,Square);
                foreach (var item in values)
                {
                    Console.Write(i+" ");// 1 4 9
                }
            }
                public int Square(int x) => x * x;
       }

Func 和Action 委托

使用泛型委托,就可以写出一组委托类型,他们可调用的方法可以拥有任意的返回类型和任意(合理)数量的参数。

//in 逆变,作为输入参数
//out 协变,作为输出参数
delegate TResult Func<out TResult>();
delegate TResult Func <in T, out TResult>(T arg);
delegate TResult Func <in T1,in T2, out TResult>(T1 arg1,T2 arg2);
 //... and so on,up to T16
 
delegate void Action();
delegate void Action <inT>(T arg);
delegate void Action <in T1, in T2>(T1 arg1,T2 arg2);
//... and so on,up to T16

通过上面的定义我们可以发现,Func和Action委托是一个简化版,它可以不用delegate来声明。

Func和Action委托一个很大的区别就是,Func委托有返回值,他有out声明输出值。而Action是一个void类型,仅仅是执行不带返回内容。

我们以Func委托为例:

static void Main(string[] args)
   {
      //此处直接使用Func委托接受实例,且内置16个参数
      //无需delegate声明
      //Func至少有一个T类型参数作为返回值。 
      //规定是将最后一个参数作为返回值。
      Func<intintint> calc = calcMul;
      Console.WriteLinecalc(12,45));
   }
private static int calcMul(int  a ,int b)
  {
      return a * b;
  }

也可以使用lamda表达式:

static void Main(string[] args)
  {
      Func<intintint> calc =(x,y)=>
         {
            return x * y;
         };
      Console.WriteLine(calc(10,45));
        }
  }

委托和接口的区别:

委托可以解决的问题,接口都可以解决。

什么情况下更适合使用委托而不是接口呢?

当下列条件之一满足时:

接口只能定义一个方法

需要多播能力

订阅者需要多次实现接口。

委托的兼容性 -委托类型

委托类型之间互不相容,即使方法签名一样。

delegate void de1();
delegate void de2();

de1 = Method1;
de2 = de1; //此时会报编译错误

委托的兼容性 -委托实例

如果委托实例拥有相同的方法目标,那么委托实例就认为是相等的。

delegate void D();de1 = Method1;de2 = Method1
Console.WriteLine( d1 == d2);//true
//因委托赋值保留了原方法的引用,
//委托内部target属性都记录同一方法应用,故相等

委托的兼容性 -参数

当你调用一个方法时,你提供的参数(argument)可以比方法的参数(parameter)定义更具体。

委托可以接受比它的方法目标更具体的参数类型,这个叫ContraVariance(逆变)。和泛型类型参数一样,委托的variance仅支持引用转换。

也可以换句话说,除去需要装箱拆箱的参数转换,委托接受的参数基本可以是向下兼容的。

//此处委托接受为string类型
delegate void StringAction(string s);
class Test
  {
    static void Main()
    {
       StringAction sa = new StringAction(ActOnObject);
       sa("hello");
    }
    static void ActionOnObject(object o)
    =>Console.WriteLine(o);//此处接收类型为object
    //输出 hello
}

委托的兼容性-返回类型

调用方法时,你可以得到一个比请求的类型更具体的类型的返回结果。

委托的目标方法可以返回比委托描述里更具体的类型的返回结果Covariance(协变)。

//此处委托返回为object类型
delegate object ObjectRetriever();
class Test
  {
    static void Main()
    {
       ObjectRetriever or = new ObjectRetriever(RetrieverString);
       object result = or;//输出hello
    }
    static void RetrieverString(object o)
    =>"hello";//返回为stirng类型
}

泛型委托类型参数的variance

Convariance,out  逆变作为参数的输出,

ContraVariance,in 协变作为参数的输入。