2011-10-14 29 views
5

我已經有了一些通用方法的接口,並且我想實現一個重載的方法來接受一個類的實例或者它的PK值(它可以是int或者GUID,但是確實有所不同)。方法重載的一般約束

我加入到相似於這些實施例的方法:

void DoSomething<TKey>(TKey key) where TKey: struct; 
    void DoSomething<TModel>(TModel model) where TModel : class; 

上的這些第二的「DoSomething的」方法名被高亮顯示,並且誤差是

類型「ISomeStuff」已定義一個名爲'DoSomething'的成員與 相同的參數類型。

我很驚訝,因爲我明確地定義了參數是不同的類型:一個是類,另一個是結構體。

爲什麼這不足以使簽名不同?

+1

的可能的複製[通用的限制,其中,T:struct和其中T:類](http://stackoverflow.com/questions/2974519/generic-constraints-where-t-struct-and-where-叔類)。另見Eric Lippert的文章[here](http://blogs.msdn.com/b/ericlippert/archive/2009/12/10/constraints-are-not-part-of-the-signature.aspx)。 –

+0

@Frederic:我怎麼錯過那個! –

+0

顯然,邊欄中的「相關」窗格也沒有選擇它,所以它可能比平常更復雜;) –

回答

3

喬恩斯基特有一個一切問題的答案:click me

報價:

的聲明僅在泛型約束不同,約束 不簽名

+1

更新後的鏈接,供將來的讀者閱讀(Jon在他的個人博客上發表了相同的文章,在這裏): http://codeblog.jonskeet.uk/2010/10/28/overloading-and-generic-constraints/ –

5

是一部分有可能做到這一點,你需要創建類似於C++的enable_if

public class ClassTag<V> where V : class { } 

public class StructTag<V> where V : struct { } 

public void Func<V>(V v, ClassTag<V> dummy = null) where V : class 
{ 
    Console.Writeln("class"); 
} 

public void Func<V>(V v, StructTag<V> dummy = null) where V : struct 
{ 
    Console.Writeln("struct"); 
} 

public void Func<V>(V? v, StructTag<V> dummy = null) where V : struct 
{ 
    Console.Writeln("struct?"); 
} 

static void Main() 
{ 
    Func("A"); 
    Func(5); 
    Func((int?)5); 
} 

它可以擴展爲使用任何不相交的where來區分過載。 唯一的缺點是它不能在另一個內部通用的方法來使用:

public static void Z1<T>(T t) // where T : class 
{ 
    Func(t); //error there 
} 

public static void Z2<T>(T t) where T : class 
{ 
    Func(t); //ok 
} 

編輯 但在這種情況下,使用dynamic的可能性來解決此限制:

public static void Z1<T>(T t) 
{ 
    Func((dynamic)t); //if `T == int` it will call "struct" version 
} 

唯一的缺點運行時間成本類似於調用Dictionary<,>索引。

+0

我喜歡這個回答。 –

1

如果有人希望一般地調用成員,而不管它是否具有類約束或結構約束,並且使它調用具有合適約束的方法,則可以定義接口IThingUser<T>以沿着任何類型T其中一個類爲值類型實現,另一個爲類類型實現。有一個靜態類ThingUsers<T>IThingUser<T>類型的靜態字段TheUser,並將其填充該字段與上面的一個類的實例,然後ThingUsers<T>.theUser將能夠在任何類型的T採取行動。

public static class GenTest93 
{ 
    public interface IThingUser<T> { void ActOnThing(T it); } 
    class StructUser<T> : IThingUser<T>, IThingUser<Nullable<T>> where T : struct 
    { 
     void IThingUser<T>.ActOnThing(T it) { System.Diagnostics.Debug.Print("Struct {0}", typeof(T)); } 
     void IThingUser<Nullable<T>>.ActOnThing(T? it) { System.Diagnostics.Debug.Print("Struct? {0}", typeof(T)); } 
    } 
    class ClassUser<T> : IThingUser<T> where T : class 
    { 
     void IThingUser<T>.ActOnThing(T it) { System.Diagnostics.Debug.Print("Class {0}", typeof(T)); } 
    } 
    static class ThingUsers<T> 
    { 
     class DefaultUser : IThingUser<T> 
     { 
      public void ActOnThing(T it) 
      { 
       Type t = typeof(T); 
       if (t.IsClass) 
        t = typeof(ClassUser<>).MakeGenericType(typeof(T)); 
       else 
       { 
        if (t.IsGenericType && t.GetGenericTypeDefinition() == typeof(Nullable<>)) 
         t = t.GetGenericArguments()[0]; 
        t = typeof(StructUser<>).MakeGenericType(t); 
       } 
       TheUser = (IThingUser<T>)Activator.CreateInstance(t); 
       TheUser.ActOnThing(it); 
      } 
     } 
     static IThingUser<T> TheUser = new DefaultUser(); 
     public static void ActOnThing(T it) {TheUser.ActOnThing(it);} 
    } 
    public static void ActOnThing<T>(T it) { ThingUsers<T>.ActOnThing(it); } 
    public static void Test() 
    { 
     int? foo = 3; 
     ActOnThing(foo); 
     ActOnThing(5); 
     ActOnThing("George"); 
    } 
} 

因此,有必要使用反射來創建的StructUser<T>ClassUser<T>一個實例,如果編譯器不知道T滿足必要的約束,但它不是太難。在第一次ActOnThing<T>()被用於特定的T,ThingUsers<T>.TheUser will be set to an instance which can be used directly for any future calls to ActOnThing(),所以表現應該非常好。

注意,如果給定Nullable<T>,該方法將創建一個StructUser<T>並將它轉換到IThingUser<Nullable<T>>,而不是試圖建立一個sometype<Nullable<T>>,因爲可空類型本身並不滿足任何約束。

1

如果你不需要泛型參數,只想在編譯時區分這些情況,你可以使用下面的代碼。

void Foo(object a) { } // reference type 
void Foo<T>(T? a) where T : struct { } // nullable 
void Foo(ValueType a) { } // value type