调用泛型方法并使用运行时设置的类型传递泛型委托参数

Call generic method and pass generic delegate parameter using Type set at runtime

编辑:这是一个Unity 2018.4项目,它使用自定义版本的Mono。脚本运行时版本为“.NET 4.x 等效”,API 兼容级别为“.NET Standard 2.0”


我有一个使用泛型的自定义事件系统,其中 AddListener<T>RemoveListener<T> 方法接受 EventDelegate<T> 参数。在代码的其他地方,给定一个在运行时确定的 Type,我想使用适当类型的委托来添加和删除侦听器,并且只知道事件何时引发。我不需要知道事件附带的任何负载。

<T> 总是派生自特定的抽象基类型可能毫无价值。

在以下示例中,我在静态 class 上有一个静态泛型方法,该方法将泛型委托作为参数。我试图在运行时使用 Type 调用泛型方法,我想将 Type 的泛型委托作为参数传递。如果在编译时没有具有正确签名的已建立方法,这可能吗?

具有通用委托和接受委托作为参数的通用方法的静态 class:

public static class GameEvent
{
   public delegate void EventDelegate<in T>(T e) where T : BaseEvent;

   public static void AddListener<T>(EventDelegate<T> listener) where T : BaseEvent
   {
      ...
   }

   public abstract class BaseEvent : EventArgs
   {
   }
}

Type 将始终派生自特定基类型。

派生的例子class:

public class CustomGameEvent : GameEvent.BaseEvent
{
   public CustomGameEvent(object a, object b) // Constructor has 0 or more parameters of unknown type.
   {
   }
}

以上设置运行良好,我有许多正在运行的自定义事件。

例如,在某些class方法中,我会添加一个侦听器并实现一个用于接收事件的委托方法,

private void SomeMethod()
{
   GameEvent.AddListener<CustomGameEvent>(this.CustomGameEventHandler);
}

private void CustomGameEventHandler(CustomGameEvent e)
{
   ... // This works and I can access properties of `e`.
}

现在进入我的问题。

在其他地方,我想使用运行时确定的 Type 调用 GameEvent.AddListner<T>,其中 Type 派生自 GameEvent.BaseEvent,但我不确定是什么在调用方法时作为参数传递。这就是我被困的地方。

我设置了一个接收 GameEvent.BaseEvent 参数而不是派生的 Type 的方法。当我希望委托方法接受派生类型时,使用接受基类型的方法是否有意义?

private Type type = typeof(CustomGameEvent); // Some type that inherits from BaseEvent.

private void GameEventHandler(GameEvent.BaseEvent e)
{
   // Do something here. Don't care that e is the base type.
   // I just want to know when the `Type` event was raised.
   // Accept any parameter that is derived from GameEvent.BaseEvent.
}

private void AddListener()
{
   var mi = typeof(GameEvent).GetMethod(nameof(GameEvent.AddListener));
   // -> void AddListener[T](EventDelegate`1)

   var miGeneric = mi?.MakeGenericMethod(type);
   // -> void AddListener[CustomGameEvent](EventDelegate`1)

   var delegateType = typeof(GameEvent.EventDelegate<>).MakeGenericType(type);
   // -> GameEvent+EventDelegate`1[CustomGameEvent]

   var parameters = new object[] { (GameEvent.EventDelegate<GameEvent.BaseEvent>)this.GameEventHandler };
   miGeneric?.Invoke(null, parameters);
   // -> When "AddListener<CustomGameEvent>()" is invoked, it receives a 
   //    "GameEvent+EventDelegate`1[GameEvent+BaseEvent]" not a
   //    "GameEvent+EventDelegate`1[CustomGameEvent]"
}

这在大多数情况下都有效,并且符合我的预期,因为我明确地将 this.GameEventHandler 转换为 (GameEvent.EventDelegate<GameEvent.BaseEvent>) 并将其作为参数传递。但我想将参数对象转换为 delegateType 中存储的类型,以便 AddListener<CustomGameEvent>() 实际上接收到 EventDelegate<CustomGameEvent> 而不是 EventDelegate<GameEvent.BaseEvent>。这可能吗?如果可能,怎么做?

如果对象被强制转换为 EventDelegate<CustomGameEvent>,甚至可以将 GameEventHandler(GameEvent.BaseEvent e) 用作委托方法,或者调用是否会由于签名不匹配而失败?

我试过使用 Convert.ChangeType(),但是 InvalidCastException: Object must implement IConvertible. 失败了。

   var parameters = new object[]
      {
         Convert.ChangeType(
            (GameEvent.EventDelegate<GameEvent.BaseEvent>)this.GameEventHandler,
            delegateType)
      };

我也尝试过使用通用的静态辅助方法 CastInto<T>(object obj),但它并没有像我希望的那样进行转换。

private void AddListener()
{
   ... // delegateType is stored

   var miCastInto = this.GetType().GetMethod(nameof(CastInto)).MakeGenericMethod(delegateType);
   // -> EventDelegate`1 CastInto[EventDelegate`1](System.Object), but is that what
   //    I want? Should it be:  EventDelegate`1[CustomGameEvent]
   //       CastInto[EventDelegate`1[CustomGameEvent]](System.Object)

   object o1 = (GameEvent.EventDelegate<GameEvent.BaseEvent>)this.GameEventHandler;
   // -> GameEvent.EventDelegate<GameEvent.BaseEvent> ... as expected.

   object o2 = miCastInto.Invoke(null, new[] { o });
   // -> GameEvent.EventDelegate<GameEvent.BaseEvent> ... not <CustomGameEvent>.

   var parameters = new object[] { castedObject }; // <- Wrong parameter type.

   ...
}

public static T CastInto<T>(object obj)
{
   return (T)obj;
}

我什至不确定我是否应该尝试巧妙地将 GameEventHandler(GameEvent.BaseEvent e) 委托给 typeDelegate。如果可以转换,那么在引发事件时 GameEventHandler(GameEvent.BaseEvent e) 是否仍会被调用?

我看到了Delegate.CreateDelegate(),但是我真的不明白是否应该在这里使用它或者应该如何使用它。

无论建立什么委托,它也将在以后与类似的 GameEvent.RemoveListener<T> 调用一起使用(使用相同的 Type),因此委托可能必须与 class 实例,以后使用时不会重新创建。我假设第二个委托创建将被视为不同的(与原始创建的引用不同)。

您可以使用 Delegate.CreateDelegate 创建特定 Type 对象的委托。

var delegateType = typeof(GameEvent.EventDelegate<>).MakeGenericType(type);
// -> GameEvent+EventDelegate`1[CustomGameEvent]

var methodInfo = typeof(YourClass).GetMethod(
    "GameEventHandler", BindingFlags.NonPublic | BindingFlags.Instance);

var parameters = new object[] { 
    Delegate.CreateDelegate(delegateType, this, methodInfo) 
};

您在此处调用的 overload 创建了一个调用实例方法的委托。第二个参数表示您希望调用该方法的实例。