Conversion to object

This is a simplified version of my code:

    UnityEvent<object> customEvent;
    public void Subscribe(UnityAction<object> action){
        customEvent.AddListener(action);
    }

    public void Broadcast(object param){
        customEvent.Invoke(param);
    }

Which I’m trying to make generic:

    UnityEvent<object> customEvent;
    public void Subscribe<T>(UnityAction<T> action){
        customEvent.AddListener(action); //PARAMETER CONVERSION ERROR
    }

How can something like this be achieved?

You would need to do something like this:

public void Subscribe<T>(UnityAction<T> action) {
  customEvent.addListener( ( arg ) => {
    action( (T)arg );
  });
}

Though I don’t really recommend going this route. Can you elaborate on why you want to have a generic method tied to a UnityEvent<object>? This would enable you to add listeners for incompatible types, potentially causing a runtime error when you broadcast.

Essentially, you can’t.

Suppose you have a function like void MyFunc(Rigidbody rb)

That means you’re only allowed to call the function if you pass it a Rigidbody as an argument. That is, you can write MyFunc(someRigidbody) but you can’t write, say, MyFunc(someSpriteRenderer)

You’re trying to pass an “object”. That’s fine if you have an Action. But if you have, say, an Action, or an Action, then that’s not allowed. In the generic version you’re trying to create, you have no idea what argument the action will expect, so you can’t guarantee that “object” will be safe (and it probably won’t).

You can pass something more specific than what the function expects. For instance, if the function wants a Component, you can pass a Rigidbody, because a Rigidbody is just one particular kind of Component. But this doesn’t work the other way around; if the function wants a Rigidbody, you can’t pass a Component.

If you have a variable of type “Component” but you’re pretty sure the object actually is a Rigidbody, you could cast it. But if it turns out that the object is NOT actually a Rigidbody, then the cast will fail. If you knew for a fact that you were always going to have a Rigidbody then your event and your Broadcast function should both (almost certainly) say “Rigidbody” instead of “object”, so I’m assuming that doesn’t apply here.

You can’t just magically glue together things with incompatible types.

That worked! Thanks!!

Actually, that was an oversimplified version of my code. Here it is in full:

public enum EventGroup1{
    BUILD_GALLERY, //
    EVENT_2
}

public enum EventGroup2{
    ANOTHER_EVENT_1, //
    ANOTHER_EVENT_2
}

public class EventsManager {
    static Dictionary<string, UnityEvent<object>> eventList;

    public static void Initialize(){
        eventList = new Dictionary<string, UnityEvent<object>>();
        AddEvent(EventGroup1.BUILD_GALLERY);
        AddEvent(EventGroup1.EVENT_2);

        AddEvent(EventGroup2.ANOTHER_EVENT_1);
        AddEvent(EventGroup2.ANOTHER_EVENT_2);
    }

    static void AddEvent(object eventName){
        eventList.Add(eventName.ToString(), new UnityEvent<object>());
        Debug.Log("Added: "+eventName);
    }

    public static void Subscribe<T>(object _eventName, UnityAction<T> action){
        if(eventList == null){
            Debug.Log("EVENT LIST is null!");
            Initialize();
        }
        string eventName = _eventName.ToString();;
        if (eventList.ContainsKey(eventName)){
            eventList[eventName].AddListener(( arg ) => {
                action((T)arg);
            });
            Debug.Log("SUBSCRIBING: "+_eventName);
        }else{
            Debug.LogWarning("Event not initialized - "+eventName);
        }
    }

    public static void Broadcast<T>(object _eventName, T param){
        string eventName = _eventName.ToString();
        if(eventList.ContainsKey(eventName)){
            Debug.Log("BROADCASTING: "+eventName);
            eventList[eventName].Invoke(param);
        }else{
            Debug.LogWarning("Broadcast failed! - "+eventName);
        }
    }  

    public static void Broadcast(object _eventName){
        Broadcast<object>(_eventName.ToString(), null);
    }
}

Usage:

// Inside some gameobject
EventsManager.Subscribe<List<Photo>>(EventGroup1.BUILD_GALLERY, FunctionToBuildGallery);

// Inside some gameobject far far away
List<Photo> PhotoList = // load photos from the server
EventsManager.Broadcast<List<Photo>>(EventGroup1.BUILD_GALLERY, PhotoList);

What you explained totally makes sense. My apologies I didn’t write the full code earlier. I have void MyFunc<T>(T param) So it should’t be an issue. Thanks anyways.

You have nothing that forces Subscribe and Broadcast to be called with the same for any given event. Someone could call Subscribe<Rigidbody>("example", SomeFunction) and then call Broadcast<SpriteRenderer>("example", someSpriteRenderer) and cause problems.

You don’t even have anything that prevents someone from subscribing to the same event twice using different types. You could do Subscribe<Foo>("example2", Function1) and also Subscribe<Bar>("example2", Function2) and then there would be NO safe way to call Broadcast on example2, because any possible argument would be invalid for one of the listeners.

In fact, it looks like this whole class essentially just removes the ordinary compile-time checks on the function calls and replaces them with an unsafe system that won’t fail until runtime if the function name or signature don’t match. (Also incidentally canceling the advantages of intellisense.) Which seems pretty unwise to me.

But hey, if you want to shoot off your own foot…