C#使用方法名创建相应的委托

前言

在程序中,我们会定义各种对象和相应的方法,在创建委托的时候,我们会将相应的调用语句写死在程序里,这种硬编码调用它们的方式可读性强,但是可扩展性不高。

为了提高程序的可扩展性,这篇文章将会介绍如何在C#中仅使用方法名就能创建对应实例的委托。

步骤

使用C#的反射

在C#中,我们可以利用反射来获取程序的程序集,再通过对应方法的名称在程序集里查找相应的方法并调用。

在程序声明时使用反射:

1
using System.Reflection;

获取对应实例的方法

假设当前有一个游戏菜单类Hud,内部包含AnimationTick方法,该方法更新传入参数hudUnit对象的动画。

1
2
3
4
5
6
7
8
9
10
11
public class Menu : Hud {

private const float gameTick = 1f / 60f;

public void AnimationTick(IHudUnit hudUnit) {
float time = hudUnit.GetAnimationTime();
time += gameTick;
hudUnit.SetAnimationTime(time);
}

}

现在在其它的代码段中,想要获取该方法的信息,则调用Type类型的GetMethod()方法,获取MethodInfo对象

1
2
3
4
var hud = new Hud();
string methodName = "AnimationTick";
MethodInfo method = hud.GetType().GetMethod(methodName);
// 注:等同于 MethodInfo method = typeof(Hud).GetMethod(content);

如果对应的对象确实存在相应的方法,则method就会存在该方法的信息。

调用方法与创建委托

上文中的method包含了该方法的许多信息,例如你可以通过Name属性查询其名称。你也可以直接通过Invoke(Object object)方法调用它(要提供实例对象)。
更加具体的信息可以查看官方文档

当然,我们的目的还是创建委托,现在,可以使用以下的语句创建之,请看CreateAnimationDelegate方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
public class Hud {

/// <summary>
/// 菜单事件的委托
/// </summary>
public delegate void MenuHandleEvent(IHudUnit unit);

}

public class HudAnimationCaller {

public MenuHandleEvent CreateAnimationDelegate(Hud hud) {
var hud = new Hud();
string methodName = "AnimationTick";
MethodInfo method = hud.GetType().GetMethod(methodName);
// 获取方法信息
var handleEvent = method.CreateDelegate(typeof(MenuHandleEvent), hud);
// 创建对应委托
return handleEvent;
}

}

现在,相关的委托就创建完成了,这个handleEvent指向在hud实例中的AnimationTick方法,你可以调用它,或者为它绑定事件,怎么样都行。

PS:如果你的方法是静态方法,那么在CreateDelegate中,应当不填入hud参数,因为静态方法的调用和实例对象无关

优缺点

优点

  • 可扩展性好,可以仅通过方法名和实例对象定位方法。

缺点

  • 性能下降,反射的运行速率低于直接调用。
  • 可读性降低,代码会变得更加抽象。(应当做好注释工作。)

题外话

理论上这篇文章到这里就结束了,但是就在我实际使用的时候,我遇到了一个问题,
将得来的委托多播后(就是把几个委托调用的方法塞到一起),我想要从这个委托里删除指定名称的委托。由于委托是引用类型,所以现在仅仅知道方法名的我,要怎么从一个委托的多个方法中删除指定的方法呢?

1
2
3
4
5
6
7
8
9
public override void RemoveEventFromHandler(string name) {
Delegate[] delegates = fired.GetInvocationList();
// fired 是之前定义的一个委托
foreach (Delegate @delegate in delegates) {
if (((MenuHandleEvent)@delegate.Target).Method.Name == name && fired != null) {
fired -= (MenuHandleEvent)@delegate;
}
}
}

在这段代码之前,有一个多播的MenuHandleEvent类型委托为fired,现在从其中删除名为name的方法,使用GetInvocationList()方法,获取方法列表,再比对方法名,像这样就能删除指定名称的委托啦!