【问题标题】:Call generic method and pass generic delegate parameter using Type set at runtime使用运行时设置的类型调用泛型方法并传递泛型委托参数
【发布时间】:2021-11-27 09:24:03
【问题描述】:

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


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

<T> 总是派生自特定的抽象基类型可能一文不值。

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

具有泛型委托和接受委托作为参数的泛型方法的静态类:

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 将始终派生自特定的基类型。

派生类示例:

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

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

例如,在某些类方法中,我会添加一个监听器并实现一个委托方法来接收事件,

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&lt;T&gt;,其中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&lt;GameEvent.BaseEvent&gt;) 并将其作为参数传递。但我想将参数对象转换为存储在delegateType 中的类型,这样AddListener&lt;CustomGameEvent&gt;() 实际上会收到EventDelegate&lt;CustomGameEvent&gt; 而不是EventDelegate&lt;GameEvent.BaseEvent&gt;。这可能吗?如果可以,怎么做?

如果对象被强制转换为EventDelegate&lt;CustomGameEvent&gt;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&lt;T&gt;(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&lt;T&gt; 调用一起使用(使用相同的Type),因此委托可能必须与类实例一起存储而不是重新创建以后使用的时候。我假设第二个委托创建将被视为不同的(与原始创建的引用不同)。

【问题讨论】:

    标签: c# unity3d generics types delegates


    【解决方案1】:

    您可以使用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 创建了一个调用实例方法的委托。第二个参数表示您希望在其上调用该方法的实例。

    【讨论】:

    • 我收到了ArgumentException: Couldn't bind to method 'GameEventHandler'。创建委托时。我之前在调查Delegate.CreateDelegate() 时可能已经尝试过,但我不知道如何使它工作。如果我在类中添加void GameEventHandler(CustomGameEvent e) 方法重载,它确实可以工作,但只是尝试使用基本事件类型作为方法参数void GameEventHandler(GameEvent.BaseEvent e) 时不起作用。
    • @MichaelRyan Huh,似乎只有在使用方法信息调用它时才考虑方差。现在回想起来,这确实是有道理的。查看编辑。
    • 啊!我从来没有想过尝试使用 MethodInfo 的重载。这很好用。谢谢!
    • 我现在看到 MS 文档明确声明此重载允许方差:“通过使用委托示例来表示 MyMethod,演示参数类型的逆变和返回类型的协方差。委托绑定到方法因为delegate的参数比方法的参数更严格(即delegate接受一个Derived的实例,总是可以安全地传递给Base类型的参数)”——这很高兴知道——再次感谢!
    • @MichaelRyan 是的,我知道这是一回事,所以我认为它也适用于字符串重载......但现在考虑一下,如果允许的话,这会引入很多CreateDelegate 的复杂性,这可能就是他们没有实现它的原因。
    猜你喜欢
    • 2021-12-19
    • 1970-01-01
    • 1970-01-01
    • 2019-04-08
    • 1970-01-01
    • 2012-12-10
    • 2023-01-26
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多