2011-10-14 35 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