2013-01-14 50 views
2

我有一些問題,我認爲,方差,我不完全理解。我有兩個類型參數的通用接口,這樣的:現在Co/contravariant接口和可分配性

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() }; 

這不WO rk,因爲IInvoker<AbstractParameter, AbstractResult>不能從IInvoker<Parameter1, Result1>(和朋友)分配,據我所知。首先,我想這是在我的界面上拍一些inout的時間(interface IInvoker<in TParameter, out TResult>),但這沒有幫助。

但我不明白爲什麼?據我所知,使用IInvoker<AbstractParameter, AbstractResult>的人應該可以撥打Invoke,對嗎?我錯過了什麼?

回答

5

問題是TResult類型參數是contra-variant,但您試圖在您的任務中同時使用它們,例如,

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

TResult共同變異,所以它的確定爲AbstractResultResult1更大的類型。但是,由於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

+0

是啊,這是'OtherParameter'調用,我沒有想過。當然,我會只使用「正確的」實現進行調用,所以我沒有理會它可能出錯的方式:)謝謝! – carlpett

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() };