2

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

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>是时候在我的界面(inoutinterface IInvoker<in TParameter, out TResult>

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

4

2 回答 2

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因为它总是有效的 for Parameter1

于 2013-01-14T23:16:08.720 回答
1

您缺少的一件事是界面中的差异声明。该接口不是变体,除非您将其声明为:

public interface IInvoker<in TParameter, out TResult>
//                        ^^             ^^^
//                        Look!          Here too!
{
    TResult Invoke(TParameter parameter);
}

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

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

这本身并不是特别有用,但关键是您可以在IInvoker<Mammal, Mammal>需要的任何地方使用 anIInvoker<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() };
于 2013-01-15T00:44:00.080 回答