Search Unity

  1. Welcome to the Unity Forums! Please take the time to read our Code of Conduct to familiarize yourself with the forum rules and how to post constructively.
  2. Dismiss Notice

Conversion to object

Discussion in 'Scripting' started by digital_monk, Jan 21, 2021.

  1. digital_monk

    digital_monk

    Joined:
    Jun 2, 2017
    Posts:
    10
    This is a simplified version of my code:

    Code (CSharp):
    1.     UnityEvent<object> customEvent;
    2.     public void Subscribe(UnityAction<object> action){
    3.         customEvent.AddListener(action);
    4.     }
    5.  
    6.     public void Broadcast(object param){
    7.         customEvent.Invoke(param);
    8.     }
    9.  
    Which I'm trying to make generic:

    Code (CSharp):
    1.     UnityEvent<object> customEvent;
    2.     public void Subscribe<T>(UnityAction<T> action){
    3.         customEvent.AddListener(action); //PARAMETER CONVERSION ERROR
    4.     }
    5.  
    How can something like this be achieved?
     
  2. Madgvox

    Madgvox

    Joined:
    Apr 13, 2014
    Posts:
    1,315
    You would need to do something like this:

    Code (CSharp):
    1. public void Subscribe<T>(UnityAction<T> action) {
    2.   customEvent.addListener( ( arg ) => {
    3.     action( (T)arg );
    4.   });
    5. }
    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.
     
  3. Antistone

    Antistone

    Joined:
    Feb 22, 2014
    Posts:
    2,833
    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<object>. But if you have, say, an Action<Rigidbody>, or an Action<SpriteRenderer>, 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.
     
  4. digital_monk

    digital_monk

    Joined:
    Jun 2, 2017
    Posts:
    10
    That worked! Thanks!!

    Actually, that was an oversimplified version of my code. Here it is in full:
    Code (CSharp):
    1. public enum EventGroup1{
    2.     BUILD_GALLERY, //
    3.     EVENT_2
    4. }
    5.  
    6. public enum EventGroup2{
    7.     ANOTHER_EVENT_1, //
    8.     ANOTHER_EVENT_2
    9. }
    10.  
    11. public class EventsManager {
    12.     static Dictionary<string, UnityEvent<object>> eventList;
    13.  
    14.     public static void Initialize(){
    15.         eventList = new Dictionary<string, UnityEvent<object>>();
    16.         AddEvent(EventGroup1.BUILD_GALLERY);
    17.         AddEvent(EventGroup1.EVENT_2);
    18.  
    19.         AddEvent(EventGroup2.ANOTHER_EVENT_1);
    20.         AddEvent(EventGroup2.ANOTHER_EVENT_2);
    21.     }
    22.  
    23.     static void AddEvent(object eventName){
    24.         eventList.Add(eventName.ToString(), new UnityEvent<object>());
    25.         Debug.Log("Added: "+eventName);
    26.     }
    27.  
    28.     public static void Subscribe<T>(object _eventName, UnityAction<T> action){
    29.         if(eventList == null){
    30.             Debug.Log("EVENT LIST is null!");
    31.             Initialize();
    32.         }
    33.         string eventName = _eventName.ToString();;
    34.         if (eventList.ContainsKey(eventName)){
    35.             eventList[eventName].AddListener(( arg ) => {
    36.                 action((T)arg);
    37.             });
    38.             Debug.Log("SUBSCRIBING: "+_eventName);
    39.         }else{
    40.             Debug.LogWarning("Event not initialized - "+eventName);
    41.         }
    42.     }
    43.  
    44.     public static void Broadcast<T>(object _eventName, T param){
    45.         string eventName = _eventName.ToString();
    46.         if(eventList.ContainsKey(eventName)){
    47.             Debug.Log("BROADCASTING: "+eventName);
    48.             eventList[eventName].Invoke(param);
    49.         }else{
    50.             Debug.LogWarning("Broadcast failed! - "+eventName);
    51.         }
    52.     }  
    53.  
    54.     public static void Broadcast(object _eventName){
    55.         Broadcast<object>(_eventName.ToString(), null);
    56.     }
    57. }
    Usage:
    Code (CSharp):
    1. // Inside some gameobject
    2. EventsManager.Subscribe<List<Photo>>(EventGroup1.BUILD_GALLERY, FunctionToBuildGallery);
    3.  
    4. // Inside some gameobject far far away
    5. List<Photo> PhotoList = // load photos from the server
    6. EventsManager.Broadcast<List<Photo>>(EventGroup1.BUILD_GALLERY, PhotoList);
     
  5. digital_monk

    digital_monk

    Joined:
    Jun 2, 2017
    Posts:
    10
    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.
     
  6. Antistone

    Antistone

    Joined:
    Feb 22, 2014
    Posts:
    2,833
    You have nothing that forces Subscribe and Broadcast to be called with the same <T> 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...