Cannot convert PointerEventData to BaseEventData

I’m trying to make a function that adds event triggers programmatically.

private void AddEventTrigger(EventTriggerType type, UnityAction<PointerEventData> callback) {
    EventTrigger.Entry entry = new EventTrigger.Entry();
    entry.eventID = type;
    entry.callback.AddListener(callback); // LINE 1
    eventTrigger.triggers.Add(entry);
}

The problem is, the compiler errors at // LINE 1 with “Cannot convert PointerEventData to BaseEventData.” PointerEventData inherits BaseEventData, so shouldn’t it be a valid input type?

The listeners implement e.g. UnityEngine.EventSystems.IDragHandler, so they require PointerEventData

Well, that’s not possible directly because this is a matter of covariance / contravariance. It always depends on the direction the data flows.

Covariance applies for delegates which return a certain value value type. You can assign a more specific method to a delegate which returns a less specific but compatible type. This for example would apply to the generic IEnumerable<T> interface as it only returns values of a certain type. So for example if you have a variable of type IEnumerable<MonoBehaviour> you can assign an IEnumerable<YourSpecificMonoBehaviourClass> to it since an IEnumerable only “returns” a value. Since the user of the variable expects to get back a MonoBehaviour, any derived class would satisfy this condition. So data flows out of the object (return value).

Contravariance on the other hand works the other way round and applies to arguments of delegates and not return values. Since the dataflow is here in the opposite direction, the relationship between derived and base classes is also reversed. So for example a variable of type Action<YourSpecificMonoBehaviourClass> can be assigned a method that only expects a MonoBehaviour. That’s because when invoking a delegate the user has to supply a compatible type based on the delegates argument. In this case our delegate type expects a YourSpecificMonoBehaviourClass instance as argument. Of course we can assign a method which a less defined argument since in the case of delegate arguments the data flows into the method.

In your specific case your “callback” is of type UnityEvent<BaseEventData> since that’s the type used internally by the UI system. So you can only assign methods to this delegate that accepts “BaseEventData” or a less derived type. Imagine this would be possible. We now have a delegate that accepts “BaseEventData” but you assigned a method that expects a PointerEventData. Nobody stops you from invoking your delegate with any other argument that is derived from BaseEventData. This just doesn’t work.

That’s why Unity jumps through several hoops in the event system to properly resolve the arguments. For example the UI system uses the ExecuteEvents class to actually invoke the handlers. It essentially has specific implementations of the Execute method which does an argument validation and casts the argument properly.

The EventTrigger class is a monobehaviour which is meant to be used as a base class for your own class. So you’re currently poking around in internally used classes by that belong to the EventTrigger component. So I’m not sure what you want to achieve here…

What you can / have to do if you want to register such a callback manually, you have to create two seperate implementations. Since the event system basically only uses either the “BaseEventData” or the “PointerEventData” having one implementation for each case should cover all your needs. In the case of the “PointerEventData” you have to create a closure that converts / validates the argument, just like the Event

 private void AddEventTrigger(EventTriggerType type, UnityAction<PointerEventData> callback) {
     EventTrigger.Entry entry = new EventTrigger.Entry();
     entry.eventID = type;
     entry.callback.AddListener((data)=>callback.Invoke(ExecuteEvents.ValidateEventData<PointerEventData>(data) ));
     eventTrigger.triggers.Add(entry);
 }

Something like that should work, I guess. Though the usage is still not really clear.