Home > Back-end >  c# compiler error when passing `this` to a delegate that accepts generic parameter
c# compiler error when passing `this` to a delegate that accepts generic parameter

Time:08-19

I have the following abstract class that I would like to leverage to create event channels for a unity project. I'm having trouble with understanding how generics work in C# (this language is new to me) and receiving a compiler error concerning passing this as an argument when invoking the listeners.

namespace EventManagers
{
    public abstract class EventSubject<T> : MonoBehaviour
    {
        public delegate void Listener<T>(T eventSubject);

        private readonly 
            List<Listener<T>> _listeners = new List<Listener<T>>();

        public void Attach(Listener<T> listener)
        {
            _listeners.Add(listener);
        }

        public void Detach(Listener<T> listener)
        {
            _listeners.Remove(listener);
        }

        public void NotifyObservers()
        {
            foreach (Listener<T> listener in _listeners)
            {
                listener(this);
            }
        }
    }
}

Error referring to the line that reads listener(this);:

 error CS1503: Argument 1: cannot convert from 'EventManagers.EventSubject<T>' to 'T'

An inheriting class looks like:

public class Selection : EventSubject<Selection> {
    private GameObject selected;
    private static Selection _instance;

    public static Selection instance
    {
        get
        {
            if (!_instance)
            {
                _instance = FindObjectOfType(typeof (Selection)) as Selection;
                if (!_instance) {
                    throw new Exception("You need a Selection in the scene");
                }
            }
            return _instance;
        }
    }

    
    public GameObject GetSelection() {
        return selected;
    }

    public void setSelection(GameObject selected) {
        this.selected = selected;
        NotifyObservers();
    }
}

My questions are:

  1. If my delegate knows to expect a generic type why is this problematic?
  2. How can I best achieve this event pattern?

CodePudding user response:

public abstract class EventSubject<T> : MonoBehaviour { ... }
public class Selection : EventSubject<Selection> { ... }

From the POV of your EventSubject class, typeof(T) could be anything at all. It could even be EventSubject<int> since you haven't provided any where constraints. The compiler has no way of knowing that you expect typeof(T) == this.GetType().

The pattern you are looking for was nicknamed Curiously recurring template pattern in C . In C#, the equivalent generic constraint is;

public abstract class EventSubject<T> : MonoBehaviour 
    where T : EventSubject<T>
{
    ...
    public void NotifyObservers()
    {
        foreach (Listener listener in _listeners)
        {
            listener((T)this);
        }
    }
}

public class Selection : EventSubject<Selection> { ... }

This is close to what you want, as it at least limits T to classes that extend EventSubject<>. But the compiler still can't prove that typeof(T) == this.GetType(), so you need an explicit cast.

This constraint also allows class Broken : EventSubject<Selection>, and there isn't any C# language feature that can prevent it. The best you can do is a runtime exception if a developer breaks your typeof(T) == this.GetType() rule.

CodePudding user response:

  1. Listener is expecting Selection instance, you're passing this which is a Listener<Selection> instance.
  2. Event pattern is built-in in c# language - https://docs.microsoft.com/en-us/dotnet/csharp/programming-guide/events/how-to-subscribe-to-and-unsubscribe-from-events You basically reimplemented it without any advantages.

CodePudding user response:

For others who may be viewing I was able to achieve a working result. My EventSubject class now appears as follows:

public abstract class EventSubject<TEventSubject> : MonoBehaviour
    {
        public delegate void Listener(TEventSubject eventSubject);

        private readonly
            List<Listener> _listeners = new List<Listener>();

        public void Attach(Listener listener)
        {
            _listeners.Add(listener);
        }

        public void Detach(Listener listener)
        {
            _listeners.Remove(listener);
        }

        public void NotifyObservers(TEventSubject eventSubject)
        {
            foreach (Listener listener in _listeners)
            {
                listener(eventSubject);
            }
        }
    }

and my Selection class now looks like this:

public class Selection : EventSubject<Selection> {
    private GameObject selected;
    private static Selection _instance;

    public static Selection instance
    {
        get
        {
            if (!_instance)
            {
                _instance = FindObjectOfType(typeof (Selection)) as Selection;
                if (!_instance) {
                    throw new Exception("You need a Selection in the scene");
                }
            }
            return _instance;
        }
    }

    
    public GameObject GetSelection() {
        return selected;
    }

    public void setSelection(GameObject selected) {
        this.selected = selected;
        NotifyObservers(this);
    }
}

I honestly never solved the riddle of why the compiler did not like listener(this) but was able to work around that by making it an argument of NotifyObservers. I would love to hear optimizations or other things people would change.

  • Related