EDIT: This is a Unity 2018.4 project, which uses a custom version of Mono. Scripting runtime version is ".NET 4.x Equivalent" and API compatibility level is ".NET Standard 2.0"
I have a custom event system using generics with AddListener<T>
and RemoveListener<T>
methods that accept a EventDelegate<T>
parameter. Elsewhere in code, given a Type
determined at runtime, I'd like to add and remove listeners using a delegate of the appropriate type and simply know when the event was raised. I don't need to know of any payload that comes with the event.
It may be worth nothing that <T>
is always derived from a specific abstract base type.
In the following examples, I have a static generic method on a static class that takes a generic delegate as a parameter. I'm trying to invoke the generic method using Type
at runtime and I'd like to pass the generic delegate of Type
as a parameter. Is this possible without having an established method with the proper signature at compile time?
Static class with generic delegate and generic method that accepts the delegate as a parameter:
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
will always be derived from a specific base type.
Example derived class:
public class CustomGameEvent : GameEvent.BaseEvent
{
public CustomGameEvent(object a, object b) // Constructor has 0 or more parameters of unknown type.
{
}
}
The above setup works well, and I have numerous custom events that are functioning.
For example, in some class method, I would add a listener and implement a delegate method for receiving the event,
private void SomeMethod()
{
GameEvent.AddListener<CustomGameEvent>(this.CustomGameEventHandler);
}
private void CustomGameEventHandler(CustomGameEvent e)
{
... // This works and I can access properties of `e`.
}
Now onto my issue.
Elsewhere, I would like to call GameEvent.AddListner<T>
using a Type
determined at runtime, where Type
is derived from GameEvent.BaseEvent
, however I'm not sure what to pass as a parameter when invoking the method. That's where I'm stuck.
I setup a method that receives a GameEvent.BaseEvent
parameter instead of the derived Type
. Does it even make sense to have a method that accepts the base type when I'd like the delegate method to accept the derived 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 works for the most part and does what I'd expect, because I'm explicitly casting the this.GameEventHandler
to (GameEvent.EventDelegate<GameEvent.BaseEvent>)
and passing that as a parameter. But I'd like to cast the parameter object to the type stored in delegateType
, so that AddListener<CustomGameEvent>()
actually receives an EventDelegate<CustomGameEvent>
and not a EventDelegate<GameEvent.BaseEvent>
. Is that possible, and if so, how?
Can GameEventHandler(GameEvent.BaseEvent e)
even be used as the delegate method if the object was cast to EventDelegate<CustomGameEvent>
or would the invoke fail due to the signature mismatch?
I've tried using Convert.ChangeType()
, however that fails with InvalidCastException: Object must implement IConvertible.
.
var parameters = new object[]
{
Convert.ChangeType(
(GameEvent.EventDelegate<GameEvent.BaseEvent>)this.GameEventHandler,
delegateType)
};
I also tried using a generic static helper method CastInto<T>(object obj)
, but that didn't cast as I hoped it would.
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;
}
I'm not even sure if I should be trying to finesse the GameEventHandler(GameEvent.BaseEvent e)
delegate to typeDelegate
. If it can be converted would GameEventHandler(GameEvent.BaseEvent e)
still be called when the event was raised?
I saw Delegate.CreateDelegate()
, but I don't really understand if that should be used here or how it should be used.
Whatever delegate is established, it would also be used with a similar GameEvent.RemoveListener<T>
call at a later time (using the same Type
), so the delegate probably has to be stored with the class instance and not re-created when used later. I'm assuming the second delegate creation would be seen as distinct (not the same reference to the original creation).
CodePudding user response:
You can use Delegate.CreateDelegate
to create a delegate of a specific Type
object.
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)
};
The overload that you are calling here creates a delegate that calls an instance method. The second parameter indicates the instance on which you want the method to be called.