首页 > 解决方案 > Unity c# - 无法从“方法组”转换为“操作”'

问题描述

情况

我的统一应用程序中有一个基本的事件管理系统,我试图让它让我调度子类的自定义事件,EventManager.Event而不必在我的处理程序中强制转换自定义事件。

当前工作示例

//PinchScale.cs
int zoomLevel = Random.Range(1, 10);
EventManager.TriggerEvent(new ZoomEvent(zoomLevel));

// Earth.cs
void ZoomEventHandler(EventManager.Event e)
{
    int zoomLevel = ((PinchScale.ZoomEvent)e).zoomLevel;
    // do some stuff
}

所需示例

void ZoomEventHandler(PinchScale.ZoomEvent e)
{
    this.LoadTiles(e.zoomLevel);
}

所需示例的错误

Assets\Scripts\Earth.cs(23,52): error CS1503: Argument 2: cannot convert from 'method group' to 'Action<EventManager.Event>'

问题

是否可以在 c# 中使用所需的示例?我这么认为是因为PinchScale.ZoomEvent子类EventManager.Event

下面的附加代码

事件管理器.cs

...

public static void StartListening(string eventType, Action<EventManager.Event> listener)
{
    if (!EventManager.instance)
    {
        Debug
            .LogError("EventManager has not been initted. Is there one added to your scene yet?");
        return;
    }

    EventListenerPair pair = new EventListenerPair(eventType, listener);
    EventManager.instance.eventDictionary.Add (pair);
}

public static void StopListening(string eventType, Action<EventManager.Event> listener)
{
    if (
        EventManager.instance == null ||
        EventManager.instance.eventDictionary.Count <= 0
    ) return;

    Array d = EventManager.instance.eventDictionary.ToArray();
    foreach (EventListenerPair e in d)
    {
        if (e.eventType == eventType && e.listener == listener)
            EventManager.instance.eventDictionary.Remove(e);
    }

    //Debug.Log("event listener count: " + EventManager.instance.eventDictionary.Count);
}

public static void TriggerEvent(EventManager.Event e)
{
    if (
        EventManager.instance == null ||
        EventManager.instance.eventDictionary.Count <= 0
    ) return;
    Array d = EventManager.instance.eventDictionary.ToArray();
    foreach (EventListenerPair pair in d)
    {
        // if (pair.eventType == e.type) pair.listener.Invoke(e);
        if (pair.eventType == e.type) pair.listener(e);
    }
}

public class Event
{
    public string type;

    public Event(string type)
    {
        this.type = type;
    }
}

class EventListenerPair
{
    public string eventType;

    public Action<EventManager.Event> listener;

    public EventListenerPair(string eventType, Action<EventManager.Event> listener)
    {
        this.eventType = eventType;
        this.listener = listener;
    }
}

...

PinchScale.cs

...

public class ZoomEvent : EventManager.Event
{
    public int zoomLevel;

    public ZoomEvent(int zoomLevel = 0) :
        base(PinchScale.ZOOM_EVENT)
    {
        this.zoomLevel = zoomLevel;
    }
}

...

地球.cs

...

void Start() {
    EventManager.StartListening(PinchScale.ZOOM_EVENT, this.ZoomEventHandler);
}

...

void ZoomEventHandler(PinchScale.ZoomEvent e)
{
    int zoomLevel = ((PinchScale.ZoomEvent)e).zoomLevel;
    // do some stuff
}

标签: c#unity3d

解决方案


是的,PinchScale.ZoomEvent子类EventManager.Event,但Action<EventManager.Event>不是子类Action<PinchScale.ZoomEvent>。如果它以这种方式工作,那将意味着多重继承(因为泛型类会从它自己的基类(Delegate在这种情况下?)和另一个泛型类(Action<EventManager.Event>)派生),我相信这是不允许的C#。

但是,您可以使您的 StartListening 和 StopListening 方法通用,如下所示:

public static void StartListening<T>(string eventType, Action<T> listener) where T : EventManager.Event 
{
    //handle the type differences here
}

然后你可以像你想要的那样使用它,但你必须在调用时显式声明泛型的类型,因为它无法推断:

EventManager.StartListening<PinchScale.ZoomEvent>(PinchScale.ZOOM_EVENT, this.ZoomEventHandler);

当然,在调度事件时,您还必须将它们的参数转换为它们接受的类型,但这一次的责任将是 on EventManager,而不是侦听器。

一个快速的模型(我将 type 参数添加到 Dispatch 以强制用户明确说明应该分派的事件。这只是我的偏好,你可以在没有它的情况下转换事件):

public class BaseEvent {}

public class DerivedEvent : BaseEvent {}

public class AnotherDerivedEvent : BaseEvent {}

class EventManager
{
    static Dictionary<BaseEvent, List<Delegate>> subscribers = new Dictionary<BaseEvent, List<Delegate>>();
    
    public static void StartListening<T>(BaseEvent @event, Action<T> arg) where T : BaseEvent
    {
        Action<BaseEvent> convertedAction = (BaseEvent be) => arg((T)be);
        if (!subscribers.ContainsKey(@event))
            subscribers.Add(@event, new List<Delegate>() { arg });
        else
            subscribers[@event].Add(arg);
    }   
    
    public static void Dispatch<T>(BaseEvent @event) where T : BaseEvent
    { 
        if (subscribers.ContainsKey(@event))
            foreach (var sub in subscribers[@event])
            {
                if (sub.GetType().GenericTypeArguments[0] != typeof(T)) //check that we didn't call with wrong type
                    throw new ArgumentException("Event not matching subscriber!");
                var action = (sub as Action<T>);
                action((T)@event);
            }           
    }   
}

和用法:

var a = new A();
var e = new DerivedEvent(); 
var e2 = new AnotherDerivedEvent();
EventManager.StartListening<DerivedEvent>(e, a.DoThing);
EventManager.Dispatch<DerivedEvent>(e); // output: Reacting to event!
EventManager.Dispatch<AnotherDerivedEvent>(e); // this line throws, because we used the wrong type argument

(甲级)

class A 
{
    public void DoThing(DerivedEvent arg)
    {
        Console.WriteLine("Reacting to event!");
    }
}

推荐阅读