Co / contravariant接口和可分配性

时间:2013-01-14 22:58:04

标签: c# covariance contravariance

我有一些问题,我认为,差异,我不完全理解。我有一个带有两个类型参数的通用接口,如下所示:

public interface IInvoker<TParameter, TResult> {
    TResult Invoke(TParameter parameter);
}

现在,就我而言,我想让TATB成为抽象类,如下所示:

public abstract class AbstractParameter {
    public int A { get; set; }
}
public abstract class AbstractResult {
    public string X { get; set; }
}

public class Parameter1 : AbstractParameter {
    public int B { get; set; }
}
public class Result1 : AbstractResult {
    public string Y { get; set; }
}
// ... Many more types

然后我想要处理IInvoker<,>的一组不同实现,所以我想我可以做这样的事情

public class InvokerOne : IInvoker<Parameter1, Result1> { /* ... */ }
public class InvokerTwo : IInvoker<Parameter2, Result2> { /* ... */ }

// ..
IInvoker<AbstractParameter, AbstractResult>[] invokers = { new InvokerOne(), new InvokerTwo() };

这不起作用,因为根据我的理解,无法从IInvoker<AbstractParameter, AbstractResult>(和朋友)分配IInvoker<Parameter1, Result1>。首先,我认为现在是时候在我的界面(in)上打了一些outinterface IInvoker<in TParameter, out TResult>,但这没有帮助。

但我不明白为什么?据我所知,使用IInvoker<AbstractParameter, AbstractResult>的任何人都应该可以致电Invoke,对吧?我错过了什么?

2 个答案:

答案 0 :(得分:5)

问题是TResult类型参数是反变量的,但是你试图在你的任务中共同使用它们,例如

IInvoker<AbstractParameter, AbstractResult> i1 = new InvokerOne();

TResult是共变体,因此AbstractResult可以是比Result1更大的类型。但是,由于TParameter具有反变量,TParameter必须是Parameter1的较小类型,而AbstractParameter不是这种情况。

如果以上内容有效,您可以这样做:

class OtherParameter : AbstractParameter { ... };
IInvoker<AbstractParameter, AbstractResult> i1 = new InvokerOne();
i1.Invoke(new OtherParameter());

这不安全。

但是你可以拥有以下内容:

public class OtherParameter1 : Parameter1 { }
IInvoker<OtherParameter1, AbstractResult> i1 = new InvokerOne();

此处OtherParameter1可以作为参数传递给Invoke,因为它始终对Parameter1有效。

答案 1 :(得分:1)

您缺少的一件事是界面中的差异声明。除非您将其声明为:

,否则界面不是变体
public interface IInvoker<in TParameter, out TResult>
//                        ^^             ^^^
//                        Look!          Here too!
{
    TResult Invoke(TParameter parameter);
}

inout关键字有助于强调差异的性质。该类型相对于in参数和关于out参数的协变而言是逆变的。换句话说,您可以这样做,假设通常的Animal : Mammal : Cat示例:

IInvoker<Mammal, Mammal> a = Whatever();
IInvoker<Cat, Mammal> b = a;
IInvoker<Mammal, Animal> c = a;

这本身并不是特别有用,但关键是你可以在IInvoker<Mammal, Mammal>IInvoker<Cat, Mammal>的任何地方使用IInvoker<Mammal, Animal>

您的问题中还缺少一些重要内容:您对IInvoker<,>实施集的具体要求是什么? (“我想处理IInvoker<,> ....的一组不同实现。”这个问题的答案将引导您找到解决方案。是否要使用从AbstractParameter继承的某些对象来调用它们?如果是这样,正如李解释的那样,如果你能做你想做的事,你会遇到一些麻烦,因为没有什么能阻止这个:

IInvoker<AbstractParameter, AbstractResult>[] invokers = { new InvokerOne(), new InvokerTwo() };
AbstractParameter[] parameters = { new ParameterOne(), new ParameterTwo() };
AbstractResult[] results = { invokers[0].Invoke(parameters[1] /* oops */), invokers[1].Invoke(parameters[0] /* oops */) };

解决该问题的一种方法是从界面中删除参数。使它成为调用者的私有字段,或者创建一个将调用者与其参数配对的类,如下所示:

interface IInvokerParameterPair<out TResult>()
    where TResult : AbstractResult
{
    TResult InvokeTheInvoker();
}

class InvokerParameterPair<TParameter, TResult> : IInvokerParameterPair<TResult>
    where TParameter : AbstractParameter 
    where TResult : AbstractResult
{
    private IInvoker<TParameter, TResult> _invoker;
    private TParameter _parameter;
    public InvokerParameterPair(IInvoker<TParameter, TResult> invoker, TParameter parameter)
    {
        _invoker = invoker;
        _parameter = parameter;
    }
    public TResult InvokeTheInvoker()
    {
        return _invoker.Invoke(_parameter);
    }
}

另一方面,如果你想做一些与Invoke方法无关的处理,那么你的调用者应该实现一些其他的通用接口或从其他一些公共基类继承,如:

public interface IProcessable { }
public interface IInvoker<in TParameter, out TResult> : IProcessable
{
    TResult Invoke(TParameter parameter);
}

public class InvokerOne : IInvoker<Parameter1, Result1> { /* ... */ }
public class InvokerTwo : IInvoker<Parameter2, Result2> { /* ... */ }

IProcessable[] invokers = { new InvokerOne(), new InvokerTwo() };

或者这个:

public interface IInvoker<in TParameter, out TResult> : IProcessable
{
    TResult Invoke(TParameter parameter);
}

public abstract class Processable { }
public class InvokerOne : Processable, IInvoker<Parameter1, Result1> { /* ... */ }
public class InvokerTwo : Processable, IInvoker<Parameter2, Result2> { /* ... */ }

Processable[] invokers = { new InvokerOne(), new InvokerTwo() };