c#generics协方差和逆变冲突

时间:2017-04-21 11:23:43

标签: c# generics covariance contravariance

我使用接口,继承和泛型在c#中编写了以下代码:

public interface IBasic
{

}

public class Basic : IBasic
{

}

public class AnotherBasic : Basic
{

}

public interface IWorker<in TBasic>
{
    void Run(TBasic basic);
}

public class Worker : IWorker<Basic>
{
    public void Run(Basic basic)
    {
        throw new System.NotImplementedException();
    }
}

public class AnotherWorker : IWorker<AnotherBasic>
{
    public void Run(AnotherBasic basic)
    {
        throw new System.NotImplementedException();
    }
}

public void Test()
{
    List<IWorker<IBasic>> workers = new List<IWorker<IBasic>>
    {
        new Worker(),
        new AnotherWorker()
    };
}

此代码的问题在于,工作者和其他工作者类不适合作为工作者和基本类的工作者父母的IWorker<IBasic>的泛型列表。问题是IWorker<in TBasic>因为运行方法签名而具有逆变性,但我需要它协变,以填充List<IWorker<IBasic>>。 run方法必须有TBasic参数,我需要这个工作列表,目的是责任链设计模式。我是否错过了某些内容,或者我找到了使协方差和逆变不相互排斥的理由?

4 个答案:

答案 0 :(得分:0)

您可以像这样初始化它:

public void Test()
{
    List<IWorker<IBasic>> workers = new List<IWorker<IBasic>>
    {
        new Worker<IBasic>(),
        new AnotherWorker<IBasic>()
    };
    workers[0].Run(new Basic());
}

答案 1 :(得分:0)

你的工人声明说“这是工人名单,每个人都可以执行任何IBasic”,但事实并非如此。

你可以尝试的是将工人可以处理的那种命令的责任转移给工人本身(事实上,这就是chain of responsibility pattern建议的那样)。

public interface IWorker
{
    bool DidRun<TBasic>(TBasic basic);
}

public class WorkerChain
{
    private readonly List<IWorker> workers = new List<IWorker>
    {
        new Worker(),
        new AnotherWorker()
    };

    public bool DidRun<T>(T basic)
    {
        return workers.Any(worker => worker.DidRun(basic));
    }
}

public class Worker : IWorker
{
    public bool DidRun<T>(T basic)
    {
        if (!(basic is Basic))
        {
            return false;
        }

        Console.WriteLine($"running {basic}");
        return true;
    }
}

public class Test
{
    public void CanRunWorkBasic()
    {
        var didRun = new WorkerChain().DidRun(new Basic());
        Debug.Assert(didRun);
    }
}

答案 2 :(得分:0)

如果要将工作人员插入列表,则需要非通用接口IWorker并且IWorker<TBasic>必须实现该接口。然后在IWorker中使用IWorker<TBasic>代替List。现在,将工作人员添加到List不再有问题。

这样我们就解决了一个问题,但遗憾的是我们还创建了另一个问题,因为我们必须两次实现Run方法。一次用于非通用接口,第二次用于通用接口。

您可以使用抽象Worker类来克服此问题,默认情况下,在调用非泛型Run时,会进行必要的检查,转换参数并将其传递给泛型{{1} }} 方法。然后你的工人可以从Run开始,每个人都可以发挥自己的作用。

在下面的示例中,我试图说明在我看来这个代码应该是什么样子。非通用Worker方法已实施explicitly,为了安全起见,我还使用了generic type constraintsRun方法只检查类型并进一步传递。

Run

答案 3 :(得分:0)

经过2天的学习和调查后,我回答了自己的问题。这是代码:

公共接口IBasic     {

}

public class Basic : IBasic
{

}

public class AnotherBasic : Basic
{

}

public interface IWorker<in TBasic>
{
    void Run(TBasic basic);
}

public class SimpleWorker : IWorker<IBasic>
{
    public void Run(IBasic basic)
    {
        throw new System.NotImplementedException();
    }
}

public class Worker : IWorker<Basic>
{
    public void Run(Basic basic)
    {
        throw new System.NotImplementedException();
    }
}

public class AnotherWorker : IWorker<AnotherBasic>
{
    public void Run(AnotherBasic basic)
    {
        throw new System.NotImplementedException();
    }
}

public class Final
{
    public void Test()
    {
        List<IWorker<AnotherBasic>> workers = new List<IWorker<AnotherBasic>>
        {
            new SimpleWorker(),
            new Worker(),
            new AnotherWorker()
        };
    }
}

CONTRAVARIANT中的TBasic,这意味着声明应尽可能最具体,如代码所示:AnotherBasic 然后接受较少派生的类型,即父项,接受,并编译代码。