简体   繁体   English

将“this”传递给接受泛型参数的委托时,c# 编译器错误

[英]c# compiler error when passing `this` to a delegate that accepts generic parameter

I have the following abstract class that I would like to leverage to create event channels for a unity project.我想利用以下抽象 class 为统一项目创建事件通道。 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.我无法理解 generics 如何在 C# 中工作(这种语言对我来说是新的),并且在调用侦听器时收到关于将this作为参数传递的编译器错误。

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);引用读取listener(this); :

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

An inheriting class looks like:继承 class 看起来像:

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?我怎样才能最好地实现这种事件模式?
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.从您的EventSubject class 的 POV 来看, typeof(T)可以是任何东西。 It could even be EventSubject<int> since you haven't provided any where constraints.它甚至可以是EventSubject<int>因为您没有提供任何where约束。 The compiler has no way of knowing that you expect typeof(T) == this.GetType() .编译器无法知道您期望typeof(T) == this.GetType()

The pattern you are looking for was nicknamed Curiously recurring template pattern in C++.您正在寻找的模式在 C++ 中被昵称为 Curiously recurring template pattern In C#, the equivalent generic constraint is;在 C# 中,等效的通用约束是;

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<> .这接近您想要的,因为它至少将T限制为扩展EventSubject<>的类。 But the compiler still can't prove that typeof(T) == this.GetType() , so you need an explicit cast.但是编译器仍然无法证明typeof(T) == this.GetType() ,因此您需要显式转换。

This constraint also allows class Broken: EventSubject<Selection> , and there isn't any C# language feature that can prevent it.此约束还允许class Broken: EventSubject<Selection> ,并且没有任何 C# 语言功能可以阻止它。 The best you can do is a runtime exception if a developer breaks your typeof(T) == this.GetType() rule.如果开发人员违反了您的typeof(T) == this.GetType()规则,您可以做的最好的事情就是运行时异常。

  1. Listener is expecting Selection instance, you're passing this which is a Listener<Selection> instance. Listener 期待Selection实例,您传递this是一个Listener<Selection>实例。
  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.事件模式内置于 c# 语言 - https://docs.microsoft.com/en-us/dotnet/csharp/programming-guide/events/how-to-subscribe-to-and-unsubscribe-from-events你基本上重新实现它没有任何优势。

For others who may be viewing I was able to achieve a working result.对于可能正在观看的其他人,我能够取得工作成果。 My EventSubject class now appears as follows:我的 EventSubject class 现在显示如下:

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:我的Selection class 现在看起来像这样:

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 .老实说,我从来没有解决编译器为什么不喜欢listener(this)的谜题,但能够通过将其作为NotifyObservers的参数来解决这个问题。 I would love to hear optimizations or other things people would change.我很想听听人们会改变的优化或其他事情。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM