【发布时间】: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<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),因此委托可能必须与类实例一起存储而不是重新创建以后使用的时候。我假设第二个委托创建将被视为不同的(与原始创建的引用不同)。
【问题讨论】:
标签: c# unity3d generics types delegates