2016-01-23 111 views
1

一方面我有以下的委託:代表「錯誤的返回類型爲」

public delegate IBar FooDelegate(string str); 
public delegate IBar FooDelegateWithRef(string str, IBar someBar); 

在另一方面,我有一個泛型類:

public class MyBaseClass 
    where T : IBar, new() 
{ 
    public FooDelegate myFunc; 
    public FooDelegateWithRef myFuncWithRef; 
} 

public class MyClass<T> : MyBaseClass 
    where T : IBar, new() 
{ 
    public MyClass() 
    { 
     myFunc = Foo; //"Wrong return type" 
     myFuncWithRef = FooWithRef; //"No overload...matches delegate" 
    } 

    public T Foo(string){ ... } 
    public T FooWithRef(string, IBar){ ... } 
} 

我的問題是,當我做了以下:

FooDelegate fooRef = MyClassInstance.Foo; 

我得到'錯誤的返回類型'錯誤。我知道代理簽名必須與方法簽名相匹配,但由於泛型中的「where」指令實際上明確指定T IBar,爲什麼它不起作用?

因此,兩個問題之一: - 爲什麼編譯器拒絕考慮方法簽名匹配? - 更重要的是,我該如何做這項工作?我寧願使用代理友好的解決方案,而不是出於約定的原因使用Func。

注意:我試過四處尋找答案,但我可能對這個問題有錯誤的措辭,所以隨時給我一巴掌如果之前已經回答過。

編輯:正如@Jonathon Chase指出的,我的示例代碼並沒有完全包裝這個問題。一個不起作用的例子可以發現here。編輯上面的代碼以反映問題。

編輯2:所有的答案對我來說非常有用,非常感謝你的時間。如果可以的話,我會檢查所有三個!

回答

3

「錯誤的返回類型」 的錯誤是因爲variancesupport value types。因此,一個class實施IBar可以轉換,而struct實施不會:

class RefTypeBar : IBar {} 
struct ValueTypeBar : IBar {} 

FooDelegate f1 = new MyClass<RefTypeBar>().Foo; // This works 
FooDelegate f2 = new MyClass<ValueTypeBar().Foo; // Fails - wrong return type 

MyClass<T>因爲T可能是一個struct內產生的錯誤,使編譯器不能保證該Foo能被分配一個FooDelegate。如果您將class約束添加到MyClass<T>,則代碼將被編譯。

public class MyClass<T> : MyBaseClass where T : class, IBar, new() 
+0

愚蠢的我認爲使用接口作爲通用約束也隱含地意味着'類' - 我不知道你可以使用接口價值類型首先解決的問題是增加類是要走的路。 – Nebu

3

在這裏你的例子必須有別的東西在進行。我目前能夠編譯和運行下面的示例,並收到預期的結果:通過改變通用

public class Program 
{ 
    public static void Main() 
    { 
     var x = new MyClass<Bar>(); 
     FooDelegate test = x.Foo; 
     test("Do It"); 
    } 
    public delegate IBar FooDelegate(string str); 
    public interface IBar { } 
    public class Bar : IBar { } 
    public class MyClass<T> where T : IBar, new() 
    { 
     T item; 
     public T Foo(string input) { Console.WriteLine(input); return item; } 
    } 
} 

DotNetFiddle

+0

你說得對,我雖然認爲這個問題會出現在我發佈的短版本中,但它沒有。 實際上我有一些微妙的東西沒有與委託意識到,這裏是我的DotNetFiddle的問題的例子:https://dotnetfiddle.net/JncI6w – Nebu

+0

哦,在這種情況下,問題是試圖申請一個非泛型委託給一個通用函數。如果您使用相同的約束使委託通用,它應該沒問題,或者試圖應用於已經聲明的實例上的方法,就像我在上面的代碼示例中所做的一樣。 –

+0

但我不能使它通用,基類只能處理接口,並且不知道它的通用子類:( 我需要犧牲通用實現的某些部分來使用接口的T,或者創建一個與委託簽名匹配的虛擬函數,並且這將破壞它的初始目的。 – Nebu

2

至少在你的DotNetFiddle例如,你可以做第一個分配給funcA可能約束到where T: Item, new()

在第二個賦值中,委託使用類型T既作爲返回類型又作爲參數類型。我相信這會導致協變和反變換的有時奇怪的影響(MSDN about Co/Contravariance): 讓我們假設一個類屬實例使用類型class SubItem : Item {...} 作爲您的類TypedFactory<T>的類型參數T

這是確定使用SubItem作爲返回類型,由於返回類型仍將(亞)型Item,和委託變量(例如funcA)仍是「滿足」由委託類型的聲明中描述的合同的。

但是如果我們使用SubItem作爲參數類型會發生什麼?委託變量(例如funcB)不能再在委託類型的聲明所承諾的每個上下文中被調用,例如,Item blubb; factory.funcB("I am alive too", blubb)是不可能的 - 類型不匹配,因爲blubb而不是類型SubItem。由於這可能發生,編譯器必須在這裏投訴。

也許這是一個選項讓你的代表通用?

using System; 

public interface IItem 
{ 
    string id {get;set;} 
} 


public class Item : IItem 
{ 
    public string id{get;set;} 
} 

public class BaseFactory<T> 
    where T: IItem, new() 
{ 
    public DelegateHolder.MakeWithID<T> funcA; 
    public DelegateHolder.MakeWithIDAndOther<T> funcB; 
} 

public class TypedFactory<T> : BaseFactory<T> 
    where T : IItem, new() 
{ 

     public TypedFactory() 
     { 
      funcA = makeNew; 
      funcB = makeNewFromOther; 
     } 

     public T makeNew(string itemId) 
     { 
      T _item = new T(); 
      _item.id = itemId; 
      return _item; 
     } 

     public T makeNewFromOther(string itemId, T other) 
     { 
      T _item = new T(); 
      _item.id = itemId; 
      return _item; 
     } 

} 

public class DelegateHolder 
{ 
    public delegate T MakeWithID<T>(string id) where T: IItem, new(); 
    public delegate T MakeWithIDAndOther<T>(string id, T other) where T: IItem, new(); 
} 

public class Program 
{ 
    public static void Main() 
    { 
     var x = new TypedFactory<Item>(); 
     BaseFactory<Item> factory = x; 

     Item someItem = factory.funcA("I am alive"); 

     Console.WriteLine(someItem.id); 
     Console.WriteLine(factory.funcB("I am alive too", someItem).id); 
    } 
} 
+0

確實,我可以使用泛型委託,但是在基類中我只需要處理接口,所以我可以'在這裏真的使用這個技巧...... :( 雖然非常翔實 - 非常感謝! – Nebu