接口继承与通用列表

时间:2014-12-16 17:39:47

标签: c# generics interface variance

我想为一个简单的项目构建一个生产者和消费者的通用系统。

我现在拥有的是

public interface IMessage     {    }
public interface Message1 : IMessage    {    }
public interface Message2 : IMessage    {    }
public interface IConsumer<T>    {    }
public interface IProducer<T>    {    }

public class Mop1 : IConsumer<Message1>, IProducer<Message2>
{
}



class Program
{
    static void Main(string[] args)
    {
        var list = new List<IConsumer<IMessage>>();
        var mop = new Mop1();
        list.Add(mop);   // Error occurs here
    }
}

最后一行给出了cannot convert from 'Mop1' to 'IConsumer<GenericPubSub.IMessage>'

之类的错误

但是Mop1实现了IMessage派生类型的IConsumer。这是一些差异问题吗?这有什么问题?

2 个答案:

答案 0 :(得分:5)

这是一个棘手的案例。你可以使用co / contravariance,但是......

我会稍微简化一下代码。只需关闭编译器即可:

public interface IMessage { }

public interface Message1 : IMessage { }


public class Mop1 : IConsumer<Message1>
{
}

public interface IConsumer<out IMessage>
{
}


class Program
{
    static void Main(string[] args)
    {
        var list = new List<IConsumer<IMessage>>();
        var mop = new Mop1();

        list.Add(mop);   // Error occurs here
    }
}

out IMessage会按照另一个答案的建议行事,但它从根本上解决了什么问题。让我说,现在你想建立你的界面:

public interface IConsumer<out IMessage>
{
    object Process (IMessage m);
}

aaaand它不会编译。因为简单地说如果你说out IMessage,就意味着返回类型应该来自IMessage,而不是参数类型。

所以你会这样:

public interface IConsumer<in IMessage>
{
    object Process (IMessage m);
}

public class Mop1 : IConsumer<Message1>
{
    public object Process (Message1 msg)
    {
        return null;
    }
}

使其编译并有效。但现在你的list.Add(mop);将无效。这是理所当然的,因为Mop1确实无法转换为IConsumer<IMessage>,因为如果它是以下代码则可能:

list[0].Process (new Message2 ());

并且这是不可能的,因为Mop1只接受Message1

所以回答这个问题。通过消息调度和纯C#编译器功能,您无法做任何有意义的事情。我知道它要去哪里,你想要用消息和东西进行静态输入。遗憾的是,您无法获得使用通用列表中特定消息的消费者列表,您必须拥有像bool CanProcess(IMessage); IMessage Process(IMessage);这样的抽象签名并在其中进行转换。您也可以使用自定义属性和反射稍微好一些,但同样,不仅仅是使用C#编译器。

答案 1 :(得分:4)

如果您计划使用这些界面实现典型的生产者,消费者模式,请先查看this answer


如果您希望IConsumer<TMessage> 成为IConsumer<TMessageBase>,其中TMessageBaseTMessage继承或实施的任何类型,那么您需要使您的通用参数协变。使用out修饰符。

public interface IConsumer<out T>    {    }

现在IConsumer<Message1>可分配给IConsumer<IMessage>,因为Message1实现了IMessage