【问题标题】:Unable to cast/match types when using generics in interfaces, why? [duplicate]在接口中使用泛型时无法转换/匹配类型,为什么? [复制]
【发布时间】:2013-12-18 13:35:22
【问题描述】:

在 IMyMessage.cs 中

public interface IMyMessage
{
}

在 IMyMessageReceiver.cs 中

public interface IMyMessageReceiver<T> where T: IMyMessage
{
    void HandleMessage(T message);
    void Subscribe();
}

在 MyMessagePublisher.cs 中

public static class MyMessagePublisher
{
    private static Dictionary<Type, List<IMyMessageReceiver<IMyMessage>>> _subscribers;

    static MyMessagePublisher
    {
         _subscribers = new Dictionary<Type, List<IMyMessageReceiver<IMyMessage>>>();
    }

    public static function Subscribe<T>(IMyMessageReceiver<T> receiver) where T: IMyMessage
    {
        Type messageType = typeof (T);
        List<IMyMessageReceiver<IMyMessage>> listeners;

        if(!_subscribers.TryGetValue(messageType, out listeners))
        {
             // no list found, so create it
             List<IMyMessageReceiver<T>> newListeners = new List<IMyMessageReceiver<T>>();
             // ERROR HERE: Can't convert List<IMyMessageReceiver<T>> to List<IMyMessageReceiver<IMyMessage>>
             _subscribers.add(messageType, newListeners);

        }

        //  I would then find the right list and add the receiver it to it but haven't got this far
    }
}

所以我希望使用一堆“IMyMessages”和“IMyMessageReceivers”来传递消息。我之前做了一个硬编码的方法,但厌倦了 100 个不同的发布/订阅函数名称,所以我想我应该用泛型很好地包装它。

我的问题是使用泛型时我无法让代码工作。即使我指定 T 类型是 IMyMessage,我也不能在任何需要 IMyMessage 的地方使用 T。也许我只是习惯于基础/扩展类,因为它在那里可以正常工作。我尝试了各种方法,从铸造到真正通用,但我总是遇到同样的问题。

【问题讨论】:

  • 你试图以协变方式使用T,但C#中的输入参数只能是逆变的
  • @Sean,那么这里有解决方案还是我应该尝试创建一个 MyMessage/MyMessageReceiver 类并扩展它们?我不太喜欢这样的声音,因为它增加了一些困难。
  • @Zeritor - 我在下面添加了一个答案。

标签: c# generics types casting


【解决方案1】:

好的,这就是我如何看待它的工作方式。由于您尝试以不受支持的方式使用协方差,因此您需要避免在某些地方使用泛型。但这样做不会失去任何类型安全性。

创建一个非泛型IMessageReceiver接口,让不能使用泛型参数的类型可以改用这个:

public interface IMyMessageReceiver
{
    void HandleMessage(IMyMessage message);

    void Subscribe();
}

public interface IMyMessageReceiver<in T> : IMyMessageReceiver
    where T : IMyMessage
{
    void HandleMessage(T message);
}

如果您愿意,可以创建一个基类来简化事情:

public abstract class MyMessageReceiverBase<T> : IMyMessageReceiver<T>
    where T : IMyMessage
{
    public abstract void HandleMessage(T message);

    public void HandleMessage(IMyMessage message)
    {
        if (!(message is T))
            throw new InvalidOperationException();
        HandleMessage((T)message);
    }

    public abstract void Subscribe();
}

然后您可以更改 IMyMessageListeners 以使用非泛型版本,因为它实际上并不需要泛型类型:

public interface IMyMessageListeners
{
    void Add(IMyMessageReceiver receiver);

    // I added this since I think this is how you're going to use it
    void Send(IMyMessage message);
}

这个类的具体是这样的:

public class MyMessageListeners : IMyMessageListeners
{
    readonly List<IMyMessageReceiver> _list = new List<IMyMessageReceiver>();

    public void Add(IMyMessageReceiver receiver)
    {
        _list.Add(receiver);
    }

    public void Send(IMyMessage message)
    {
        foreach (var listener in _list)
            listener.HandleMessage(message);
    }
}

然后(最终),您的静态类将如下所示:

public static class MyMessagePublisher
{
    static readonly Dictionary<Type, IMyMessageListeners> _subscribers = new Dictionary<Type, IMyMessageListeners>();

    // I added this too, since I think this is how you intend to use it
    public static void Publish<T>(T message) where T : IMyMessage
    {
        Type messageType = typeof(T);
        IMyMessageListeners listeners;

        if (_subscribers.TryGetValue(messageType, out listeners))
            listeners.Send(message);
    }

    public static void Subscribe<T>(IMyMessageReceiver<T> receiver) where T : IMyMessage
    {
        Type messageType = typeof(T);
        IMyMessageListeners listeners;

        if (!_subscribers.TryGetValue(messageType, out listeners))
        {
            // no list found, so create it
            listeners = new MyMessageListeners();
            _subscribers.Add(messageType, listeners);
        }

        listeners.Add(receiver);
    }
}

你可以像这样使用你的静态类:

MyMessagePublisher.Subscribe(new FooMessageReceiver());
MyMessagePublisher.Publish(new FooMessage());

【讨论】:

  • 您的解决方案与使用 List> 相同,无论如何都可以使用。如果使用 IMyMessageListeners 插入 IMyMessageListeners 会起作用吗?
  • 呸,你是对的,它是相同的,Add(receiver) 调用无论哪种方式都不起作用。我会重新考虑这一点,但如果我无法提出任何建议,我可能会撤回我的答案。
  • '' 定义中的 in/out 做什么?如果您认为这不起作用,我将暂缓尝试。我觉得我做错了。
  • out 使类型参数“协变”,in 使其“逆变”。在评论中谈论太多,谷歌它或start here
  • @Zeritor 你可以创建一个接口IMyMessageReceiver&lt;T1,T2&gt; : IMyMessageReceiver&lt;T1&gt;并添加方法HandleMessage(T2 message)。你需要创建一个类似的基类,但这会让你得到你想要的我想
【解决方案2】:

泛型不支持协方差。因此,即使 T 实现了 IMyMessage,也无法将 IMyMessageReceiver T 强制转换为 IMyMessageReceiver IMyMessage。这就是你得到错误的原因。

我认为您不应该在 IMyMessageReceiver 接口中使用泛型。我不确定您要达到的目标,但也许这样的事情可以解决问题:

public interface IMyMessageReceiver
{
    void HandleMessage(IMyMessage message);
    void Subscribe();
}

public class MyMessageReceiver<T> : IMyMessageReceiver where T: IMyMessage
{
    void IMyMessageReceiver.HandleMessage(IMyMessage message)
    {
        HandleMessage(message as T);
    }

    public void HandleMessage(T message) {...}
    public void Subscribe() {...}
}

【讨论】:

    【解决方案3】:

    鉴于您知道类型将始终为 IMyMessageReceiver&lt;T&gt;,您可以通过将所有内容存储为对象并强制转换来模拟行为:

    private static Dictionary<Type, List<object>> _subscribers;
    
    
        public static function Subscribe<T>(IMyMessageReceiver<T> receiver) where T: IMyMessage
        {
          Type messageType = typeof (T);
          List<object> listeners;
    
          if(!_subscribers.TryGetValue(messageType, out listeners))
          {
            // no list found, so create it
            List<object> newListeners = new List<object>();
            newListeners.Add(receiver)
            _subscribers.add(messageType, newListeners);
    
          }
    
          var messageReceivers = listeners.Cast<IMyMessageReceiver<T>>();
    }
    

    既然您知道字典中的列表将始终属于给定类型,您可以放心地转换它们!

    【讨论】:

    • 泛型的重点是避免使用动态转换的丑陋对象容器!
    • @AdrienBuet - 是的,但有时你必须出卖你的灵魂!
    【解决方案4】:

    在这种情况下KeyedByTypeCollection&lt;T&gt; 是合适的:

    public interface IMyMessage
    {
    }
    
    public interface IMyMessageReceiver<T> where T : IMyMessage
    {
        void HandleMessage(T message);
        void Subscribe();
    }
    
    public static class MyMessagePublisher
    {
        private static readonly KeyedByTypeCollection<IList> Subscribers;
    
        static MyMessagePublisher()
        {
            Subscribers = new KeyedByTypeCollection<IList>();
        }
    
        public static void Subscribe<T>(IMyMessageReceiver<T> receiver) where T : IMyMessage
        {
            List<IMyMessageReceiver<T>> listeners = Subscribers.Find<List<IMyMessageReceiver<T>>>();
    
            if (listeners == null)
            {
                listeners = new List<IMyMessageReceiver<T>>();
                Subscribers.Add(listeners);
            }
    
            // Now you can use the listeners list
    
            listeners.Add(receiver);
        }
    }
    

    【讨论】:

      猜你喜欢
      • 2016-10-20
      • 1970-01-01
      • 2023-01-11
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2019-09-16
      • 2011-12-28
      相关资源
      最近更新 更多