在.NET平台下,委托类型用来定义和响应应用程序中的回调。事实上,.NET委托类型是一个类型安全的对象,指向可以以后调用的其他方法。和传统的C++函数指针不同,.NET委托是内置支持多路广播和异步方法调用的对象。

委托类型包含3个重要信息:

  • 它所调用的方法的名称
  • 该方法的参数
  • 该方法的返回值

1.定义一个委托类型

// 这个委托可以指向任何传入两个整数,返回整数的方法 
public delegate int BinaryOp(int x,int y);

创建一个委托类型时,需要使用delegate关键字。当C#编译器处理委托类型时,它先自动产生一个派生自System.MulticastDelegate的密封类。

通过ildasm.exe来查看BinaryOp委托,如下图

编译器是如何确切的定义 Invoke(),BeginInvoke(),EndInvoke() 的呢?让我们看看下面这段代码:

sealed class BinaryOp : System.MulticastDelegate
{
  public int Invoke(int x,int y);   public IAsyncResult BeginInvoke(int x,int y,AsyncCallback cb,object state);   public int EndInvoke(IasyncResult result);
}

Invoke() 方法定义的参数和返回值完全匹配BinaryOp委托的定义。

BeginInvoke() 成员签名的参数也基于BinaryOp委托;但BenginInvoke()方法将总是提供最后两个参数(AsyncCallback,object),用于异步方法的调用。

EndInvoke() 方法的返回值与初始的委托声明相同,总是以一个实现了IasyncResult接口的对象作为其唯一的参数。

我们再定义一个 带有 out/ref 参数的委托类型如下:

public delegate string MyOtherDelegate(out bool a,ref bool b,int c);

试想一下,产生的代码还和上面一样吗?好。

sealed class MyOtherDelegate: System.MulticastDelegate
{
  public string Invoke(out bool a,ref bool b,int c);   public IAsyncResult BeginInvoke(out bool a,ref bool b,int c,AsyncCallback cb,object state);   public stringEndInvoke(out bool a,ref bool b,IasyncResult result);
}

我们发现,Invoke() 和 BeginInvoke() 方法的签名不出所料,但 EndInvoke() 略有差异,其中包括了委托类型定义的所有 out/ref 参数。

2.System.Delegate和System.MulticastDelegate基类

使用关键字创建委托的时候,也就间接的声明了一个派生自System.MulticastDelegate的类。

下面是System.MulticastDelegate部分成员源代码:

public abstract class MulticastDelegate : Delegate
{
// 用来在内部管理委托所维护的方法列表
private Object _invocationList;
private IntPtr _invocationCount; // 返回所指向的方法的列表
public override sealed Delegate[] GetInvocationList()
{
Contract.Ensures(Contract.Result<Delegate[]>() != null); Delegate[] del;
Object[] invocationList = _invocationList as Object[];
if (invocationList == null)
{
del = new Delegate[];
del[] = this;
}
else
{
// Create an array of delegate copies and each
// element into the array
int invocationCount = (int)_invocationCount;
del = new Delegate[invocationCount]; for (int i = ; i < invocationCount; i++)
del[i] = (Delegate)invocationList[i];
}
return del;
} // 重载的操作符
public static bool operator ==(MulticastDelegate d1, MulticastDelegate d2)
{
if ((Object)d1 == null)
return (Object)d2 == null; return d1.Equals(d2);
} public static bool operator !=(MulticastDelegate d1, MulticastDelegate d2)
{
if ((Object)d1 == null)
return (Object)d2 != null; return !d1.Equals(d2);
}
}

MulticastDelegate

下面是System.Delegate部分成员源代码:

 public abstract class Delegate : ICloneable, ISerializable
{
// 与函数列表交互的方法
public static Delegate Combine(Delegate a, Delegate b)
{
if ((Object)a == null) // cast to object for a more efficient test
return b; return a.CombineImpl(b);
} public static Delegate Combine(params Delegate[] delegates)
{
if (delegates == null || delegates.Length == )
return null; Delegate d = delegates[];
for (int i = ; i < delegates.Length; i++)
d = Combine(d,delegates[i]); return d;
} public static Delegate Remove(Delegate source, Delegate value)
{
if (source == null)
return null; if (value == null)
return source; if (!InternalEqualTypes(source, value))
throw new ArgumentException(Environment.GetResourceString("Arg_DlgtTypeMis")); return source.RemoveImpl(value);
} public static Delegate RemoveAll(Delegate source, Delegate value)
{
Delegate newDelegate = null; do
{
newDelegate = source;
source = Remove(source, value);
}
while (newDelegate != source); return newDelegate;
} // 重载的操作符
public static bool operator ==(Delegate d1, Delegate d2)
{
if ((Object)d1 == null)
return (Object)d2 == null; return d1.Equals(d2);
} public static bool operator != (Delegate d1, Delegate d2)
{
if ((Object)d1 == null)
return (Object)d2 != null; return !d1.Equals(d2);
} // 扩展委托目标的属性
public MethodInfo Method
{
get
{
return GetMethodImpl();
}
}
public Object Target
{
get
{
return GetTarget();
}
} }

Delegate

System.Delegate / System.MulticastDelegate 部分成员
 继承成员      作用
Method 此属性返回System.Reflection.MethodInfo对象,用以表示委托维护的静态方法的详细信息
Target 如果方法调用是定义在对象级别的(而非静态方法),Target返回表示委托维护的方法对象。如果Target返回null,调用的方法是一个静态成员
Combine() 此静态方法给委托维护列表添加一个方法,在C#中,使用重载+=操作符作为简化符号调用此方法
GetInvocationList() 此方法返回一个System.Delegate类型的数组,其中数组中的每个元素都表示一个可调用的特定方法

Remove()

RemoveAll()

 这些静态从调用列表中移除一个(所有)方法;在C#中,Remove方法可以通过使用重载-=操作符来调用

一. 创建简单的委托

好了,了解了以上这些委托的基础信息,我们开始创建属于我们的第一个委托:

public class MyDelegate
{
// 这个委托可以指向任何传入两个整数并返回一个整数的方法
private delegate int BinaryOp(int x, int y); // BinaryOp委托将指向的方法
private static int Add(int x, int y) => x + y; public static void Show()
{
// 创建一个指向 Add() 方法的BinaryOp委托对象
BinaryOp b = new BinaryOp(Add); // 使用委托对象间接调用Add()方法的两种方法:
Console.WriteLine($"b(2, 3)-->{b(2, 3)}");
Console.WriteLine($"b.Invoke(2,3)-->{ b.Invoke(2, 3)}"); DisplayDelegateInfo(b);
} // 将输出委托调用列表那个每个成员的名称
public static void DisplayDelegateInfo(Delegate delObj)
{
foreach (Delegate item in delObj.GetInvocationList())
{
// 若delObj委托指向的是静态方法,则 item.Target 为null
Console.WriteLine($"{item.Method},{item.Target}");
}
}
}

二.使用委托发送对象状态通知

以上的示例纯粹用来说明委托的作用,因为仅为两个数相加创建一个委托没有多大必要,为了更现实的委托应用,我们用委托来定义Car类,它可以通知外部实体当前引擎的状态。步骤如下:

  1. 定义将通知发送给调用者的委托类型
  2. 声明Car类中的每个委托类型的成员变量
  3. 在Car上创建辅助函数使调用者能指定由委托成员变量保存的方法
  4. 修改Accelerate()方法以在适当的情形下调用委托的调用列表。

定义Car类:

    public class Car
{
// 内部状态数据
public int CurrentSpeed { get; set; } public int MaxSpeed { get; set; } public string PetName { get; set; } // 汽车是否可用
private bool CarIsDead; public Car()
{
MaxSpeed = ;
} public Car(string name, int maxSp, int currSp)
{
CurrentSpeed = currSp;
MaxSpeed = maxSp;
PetName = name;
} // 1. 定义委托类型
public delegate void CarEngineHandler(string msgForCaller); // 2. 定义每个委托类型的成员变量
private CarEngineHandler listOfHandlers; // 3. 向调用者添加注册函数
public void RegisterWithCarEngine(CarEngineHandler methodCall)
{
listOfHandlers = methodCall;
} // 4. 实现Accelerate()方法以在某些情况下调用委托的调用列表 public void Accelerate(int delta)
{
if (CarIsDead)
{
if (listOfHandlers != null)
{
listOfHandlers("sorry,this car is dead///");
}
}
else
{
CurrentSpeed += delta; if ((MaxSpeed - CurrentSpeed) == && listOfHandlers != null)
{
listOfHandlers("careful buddy ! gonna blow !");
} if (CurrentSpeed >= MaxSpeed)
{
CarIsDead = true;
}
else
{
Console.WriteLine($"CurrentSpeed={CurrentSpeed}");
}
}
}
}

注意:我们在调用 listOfHandlers 成员变量保存方法之前,需要坚持该变量是否为空,因为在调用 RegisterWithCarEngine() 方法分配这些对象是 调用者的任务。如果调用者没有调用这个方法而试图调用 listOfHandlers ,将引发空异常。

我们来看看具体该如何调用:

public class CarDelegate
{
public static void Show()
{
Console.WriteLine("******Delegate Car******"); // 创建一个Car对象
Car c1 = new Car("bmw", , ); // 告诉汽车,它想要向我们发送信息时条用哪个方法
c1.RegisterWithCarEngine(new Car.CarEngineHandler(OncarEngineEvent)); // 加速,触发事件
Console.WriteLine("******Speeding up******");
for (int i = ; i < ; i++)
{
c1.Accelerate();
}
} // 要传入事件的方法
private static void OncarEngineEvent(string msg)
{
Console.WriteLine($"=>{msg}");
}
}

注意:RegisterWithCarEngine() 方法要求传入一个内嵌的 CarEngineHandler 的委托的实例,与其他委托一样,我们指定一个“所指向的方法”作为构造函数的参数。OncarEngineEvent() 方法与相关的委托完全匹配,也包含 string 类型的输入参数和 void 返回类型。

运行结果如下图:

a.支持多路广播

一个委托对象可以维护一个可调用方法的列表而不只是单独的一个方法。给委托对象添加多个方法时,重载+=操作符即可。为上面的Car类支持多路广播,修改RegisterWithCarEngine()方法,具体如下:

// 现在支持多路广播
// 注意蓝色"+="操作符,而非赋值操作符"="
public void RegisterWithCarEngine(CarEngineHandler methodCall)
{
listOfHandlers += methodCall;
}

如此一来,调用者就可以为同样的回调注册多个目标对象了,这个,第二个采用打印大写的传入信息。

public class CarDelegate
{
public static void Show()
{
Console.WriteLine("******Delegate Car******"); // 创建一个Car对象
Car c1 = new Car("bmw", , ); // 为通知注册多个目标
c1.RegisterWithCarEngine(new Car.CarEngineHandler(OncarEngineEvent));
c1.RegisterWithCarEngine(new Car.CarEngineHandler(OncarEngineEcentToUpper)); // 加速,触发事件
Console.WriteLine("******Speeding up******");
for (int i = ; i < ; i++)
{
c1.Accelerate();
}
} //现在在发送通知信息时,Car类将调用以下这两个方法
private static void OncarEngineEvent(string msg)
{
Console.WriteLine($"=>{msg}");
} private static void OncarEngineEcentToUpper(string msg)
{
Console.WriteLine($"message form car object=>{msg.ToUpper()}");
}
}

b.从委托调用列表中移除成员
Dlegate类还定义了静态Remove()方法,允许调用者动态从委托对象的调用列表中移除方法,这样一来,调用者就在运行是简单的“退订”某个已知的通知。你可以直接使用Delegate.Remove(),也可以 “-=” 操作符。

为Car类添加 UnRegisterWithCarEngine() 方法,允许调用者从调用列表中移除某个方法。

public void UnRegisterWithCarEngine(CarEngineHandler methodCall)
{
listOfHandlers -= methodCall;
}

调用如下:

public static void Show()
{
Console.WriteLine("******Delegate Car******"); // 创建一个Car对象
Car c1 = new Car("bmw", , ); // 告诉汽车,它想要向我们发送信息时条用哪个方法
c1.RegisterWithCarEngine(new Car.CarEngineHandler(OncarEngineEcent)); // += 先绑定委托对象,稍后注销
Car.CarEngineHandler handler2 = new Car.CarEngineHandler(OncarEngineEcentToUpper);
c1.RegisterWithCarEngine(handler2); // 加速,触发事件
Console.WriteLine("******Speeding up******");
for (int i = ; i < ; i++)
{
c1.Accelerate();
} // -= 注销第二个处理程序
c1.UnRegisterWithCarEngine(handler2); // 看不到大写的消息了
Console.WriteLine("******Speeding up******");
for (int i = ; i < ; i++)
{
c1.Accelerate();
}
}

c.方法组转换语法

我们显示的创建了 Car.CarEngineHandler 委托对象的实例,以注册和注销引擎通知:

Car c1 = new Car("bmw", , );

c1.RegisterWithCarEngine(new Car.CarEngineHandler(OncarEngineEcent));
Car.CarEngineHandler handler2 = new
Car.CarEngineHandler(OncarEngineEcentToUpper);
c1.RegisterWithCarEngine(handler2); c1.UnRegisterWithCarEngine(handler2);

如果要调用MulticastDelegate或Delegate总继承的任何成员,手工创建一个委托变量是最直接的方式。但是大多数情况下,我们并不依靠委托对象。我们可以使用C#提供的方法组转换的方法,它允许我们在调用以委托作为参数的方法时直接提供了与委托期望的签名想匹配的方法的名称(返回 void,参数 string),而不是创建委托对象。

public class CarDelegateMethodGroupConversion
{
public static void Show()
{
Console.WriteLine("******Delegate Car******");
Car c1 = new Car("slogbug", , );
     
     // 注册简单的类型名称
c1.RegisterWithCarEngine(CallMethere); Console.WriteLine("******Speeding up******");
for (int i = ; i < ; i++)
{
c1.Accelerate();
}      // 注册简单的类型名称
c1.UnRegisterWithCarEngine(CallMethere); Console.WriteLine("******Speeding up******");
for (int i = ; i < ; i++)
{
c1.Accelerate();
}
} private static void CallMethere(string msg) => Console.WriteLine($"=>{msg}");
}

四:泛型委托

如果我们希望定义一个委托类型来调用任何返回void并且接受单个参数的方法。如果这个参数可能会不同,我们就可以通过类型参数来构建。

下面我们看一个小示例:

public class GenericDelegate
{
public delegate void MyGenericDelegate<T>(T arg); public static void Show()
{
     // 注册目标
MyGenericDelegate<string> stringTarget = new MyGenericDelegate<string>(StringTarget);
stringTarget("i am ok"); MyGenericDelegate<int> intTarget = new MyGenericDelegate<int>(IntTarget);
intTarget.Invoke();
} static void StringTarget(string arg) => Console.WriteLine($"StringTarget--> {arg.ToUpper()}"); static void IntTarget(int arg) => Console.WriteLine($"IntTarget--> {++arg}"); }

a. 泛型Action<> 和 Func<> 委托
从以上的学习中我们已经了解到,使用委托在应用程序中进行回调需要遵循以下步骤:

  • 自定义一个与要指向的方法格式相匹配的委托
  • 创建自定义委托的实例,将方法名作为构造函数的参数
  • 通过调用委托对象的Invoke()方法来间接调用该方法

其实,这种方式通常会构建大量只用于当前任务的自定义委托。当委托名无关紧要的时候,我们可以使用框架内置的Action<> 和 Func<> 泛型委托,可指向至多传递16个参数的方法。

Action<>:无返回值: 定义 public delegate void Action<...>

public class MyActionDelegate
{
public static void Show()
{
// 使用Action<>委托来指向 DisplayMessage()
Action<string, ConsoleColor, int> actionTarget = new Action<string, ConsoleColor, int>(DisplayMessage);
actionTarget("actionTarget", ConsoleColor.Red, );
} // Action<> 委托的一个目标
private static void DisplayMessage(string msg, ConsoleColor txtColor, int printCount)
{
ConsoleColor previous = Console.ForegroundColor;
Console.ForegroundColor = txtColor; for (int i = ; i < printCount; i++)
{
Console.WriteLine(msg);
} Console.ForegroundColor = previous;
}
}

运行效果如下图:

Func<>:有返回值 public delegate TResult Func<..., out TResult>

public class FuncDelagate
{
public static void Show()
{
Func<int, int, int> funcTarget = new Func<int, int, int>(Add);
int result = funcTarget(, );
Console.WriteLine(result); Func<int, int, string> funcTarget2 = new Func<int, int, string>(SumToString);
string sumStr = funcTarget2(, );
Console.WriteLine(sumStr);
} static int Add(int x, int y) => x + y; static string SumToString(int x, int y) => (x + y).ToString();
}

运行结果:3,7

鉴于 Action<> 和 Func<> 节省了手工创建自定义委托的步骤,but 总是应该使用他们吗?

答案:“视情况而定”。

很多情况下 Action<> 和 Func<> 都是首选,但如果你觉得一个具有自定义名称的委托更有助于捕获问题范畴,那么构建自定义委托不过就是一行代码的事儿。

注:Linq中就大量的用到了 Action<> 和 Func<>。

本文参考《精通C#》

学无止境,望各位看官多多指教。

C#委托(Delegate)学习日记的更多相关文章

  1. .Net 委托 delegate 学习

    一.什么是委托: 委托是寻址方法的.NET版本,使用委托可以将方法作为参数进行传递.委托是一种特殊类型的对象,其特殊之处在于委托中包含的只是一个活多个方法的地址,而不是数据.   二.使用委托: 关键 ...

  2. C#事件(Event)学习日记

    event 关键字的来由,为了简化自定义方法的构建来为委托调用列表增加和删除方法. 在编译器处理 event 关键字的时候,它会自动提供注册和注销方法以及任何必要的委托类型成员变量. 这些委托成员变量 ...

  3. IOS开发使用委托delegate在不同窗口之间传递数据

    IOS开发使用委托delegate在不同窗口之间传递数据是本文要介绍的内容,主要是来讲解如何使用委托delegate在不同窗口之间传递数据,具体内容来看详细内容.在IOS开发里两个UIView窗口之间 ...

  4. [.NET] C# 知识回顾 - 委托 delegate (续)

    C# 知识回顾 - 委托 delegate (续) [博主]反骨仔 [原文]http://www.cnblogs.com/liqingwen/p/6046171.html 序 上篇<C# 知识回 ...

  5. [C#] C# 知识回顾 - 委托 delegate

    C# 知识回顾 - 委托 delegate [博主]反骨仔 [原文]http://www.cnblogs.com/liqingwen/p/6031892.html 目录 What's 委托 委托的属性 ...

  6. Linux学习日记-使用EF6 Code First(四)

    一.在linux上使用EF 开发环境 VS2013+mono 3.10.0 +EF 6.1.0 先检测一下EF是不是6的 如果不是  请参阅 Linux学习日记-EF6的安装升级(三) 由于我的数据库 ...

  7. C# 委托Delegate(一) 基础介绍&用法

    本文是根据书本&网络 前人总结的. 1. 前言 定义&介绍: 委托Delegate是一个类,定义了方法的类型, 使得可以将方法当做另一个方法的参数来进行传递,这种将方法动态地赋给参数的 ...

  8. 为什么不能把委托(delegate)放在一个接口(interface)当中?

    stackoverflow上有人问,为什么不能把委托放在一个接口当中? 投票最多的第一个答案第一句话说,“A Delegate is just another type, so you don't g ...

  9. C# 代理/委托 Delegate

    本文转载自努力,努力,努力 1. 委托的定义:委托是函数的封装,它代表一"类"函数.他们都符合一定的签名:拥有相同的参数列表,返回值类型.同时,委托也可以看成是对函数的抽象,是函数 ...

  10. android学习日记05--Activity间的跳转Intent实现

    Activity间的跳转 Android中的Activity就是Android应用与用户的接口,所以了解Activity间的跳转还是必要的.在 Android 中,不同的 Activity 实例可能运 ...

随机推荐

  1. xcode6下使用autolayout+sizeclass实践

    历史车轮滚滚向前,将autolayout配合sizeclass做布局的方式推上了主流,虽然有点晚,但最终还是进行了一次完整的实践,特此记录一下: 因为网上已经有很多博客介绍了autolayout配合s ...

  2. iOS中Git的使用

    打开终端: 查看Git的版本的终端命令:git —version 输入:ssh 查看是否已经存在ssh. 如果存在,先将已有的ssh备份,或者将新建的ssh生成到另外的目录下 如果不存在,通过默认的参 ...

  3. 【Android】数据库的简单应用——创建数据库

    SQLiteOpenHelper是一个抽象类,要使用它必须写一个类继承它.SQLiteOpenHelper有两个抽象方法onCreate()和onUpgrade(),我们要在类里面重写这两个方法来实现 ...

  4. sqlmap

    http://192.168.136.131/sqlmap/mysql/get_int.php?id=1 当给sqlmap这么一个url的时候,它会: 1.判断可注入的参数 2.判断可以用那种SQL注 ...

  5. ssh 无密码登录远程服务器

    在讲下文之前,我都默许大家都已经生成了自己的ssh公钥和密钥,在自己的~/.ssh 目录下面,如果没有,请使用 ssh-keygen -t rsa -C "你的邮箱" 命令生成 1 ...

  6. javascript moveTo() 函数

    moveTo-- 移动窗体左上角到相对于屏幕左上角的(x,y)点,当使用负数做为参数时会吧窗体移出屏幕的可视区域 moveTo,中文"移动到"的意思 引用网址:http://www ...

  7. 使用LuaInterface遇到的编码问题

    今天使用LuaInterface加载脚本时忽然报“未知字符”错误信息!于是检查文件编码 将其修改为“US ASCII” 就好了.

  8. Android开发必备:颜色选择

      AA 指定透明度. 00 是完全透明. FF 是完全不透明.超出取值范围的值将被恢复为默认值.    ffff00 ffff33 ffff66 ffff99 ffffcc ffffff ffcc0 ...

  9. Linked Server for SQL Server 2012(x64) to Oracle Database 12c(x64)

    因为把两台数据库装了同一台机机器上,所以没有安装oracle Client的部分,Oracle部分使用netca创建的Net Service Name,使用tnsping以及登入方式的确认用户权限的以 ...

  10. IIS本地部署项目出错

    今天,打开部署在本地IIS的项目发现出错了.报的错,跟没有连接网络一样的.我当时有点懵,过一会儿再静下心来,想想这是什么原因. 第一步,把所有部署的项目,都打开看了一下,方便找出对比. 发现,绑定了I ...