2011-10-14 22 views
5

Mam interfejs z kilkoma rodzajowymi metodami i chciałem zaimplementować metodę z przeciążeniami, aby zaakceptować instancję klasy lub jej wartość PK (która jest int lub GUID, ale różni się).Ogólne ograniczenia przeciążenia metod:

dodałem do metod podobnych do tych przykładów:

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

do „DoSomething” nazwa metody na drugim z nich jest podświetlona, ​​a błąd jest

Type „ISomeStuff” już definiuje element o nazwie "DoSomething" z tymi samymi typami parametrów.

Jestem zaskoczony tym, ponieważ jasno określiłem, że parametry mają być innego typu: jeden jest klasą, a drugi strukturą.

Dlaczego to nie wystarczy, aby podpisy były różne?

+1

Możliwe duplikat [Generic ograniczeń, gdzie T struct i gdzie T: klasa] (http://stackoverflow.com/questions/2974519/generic-constraints-where-t-struct-and-where- klasa t). Zobacz także artykuł Erica Lipperta [tutaj] (http://blogs.msdn.com/b/ericlippert/archive/2009/12/10/constraints-are-not-part-of-the-signature.aspx). –

+0

@Frederic: Jak ja tęskniłem za tym !!! –

+0

Wygląda na to, że "Powiązane" okienko na pasku bocznym również go nie wybrało, więc może być trudniejsze niż zwykle;) –

Odpowiedz

3

Jon Skeet ma odpowiedź na wszystko: click me

cytat:

deklaracje różnią się tylko w ogólnych ograniczeń, a ograniczenia nie są częścią podpisu

+1

Zaktualizowany link dla przyszłych czytelników (Jon opublikował ten sam artykuł na swoim osobistym blogu, tutaj): http://codeblog.jonskeet.uk/2010/10/28/overloading-and-generic-constraints/ –

5

Is możliwe do zrobienia, potrzebujesz stworzyć coś w rodzaju: enable_if z C++

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

Można go rozszerzyć, aby użyć dowolnego rozłącznego where, aby rozróżnić przeciążenia. Jedyną wadą jest to, że mogę być używany wewnątrz innej metody rodzajowe:

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 
} 

edycji Ale istnieje możliwość wykorzystania dynamic w tym przypadku w celu obejścia tego ograniczenia:

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

Jedyną wadą jest to koszt czasu uruchomienia podobny do wywołania indeksu Dictionary<,>.

+0

Uwielbiam to odpowiedź. –

1

Jeśli chce się wywołać człon ogólnie, niezależnie od tego, czy ma ograniczenia klasy lub ograniczenie Struct i to wywołać metodę z odpowiednim ograniczeniem, można zdefiniować interfejs IThingUser<T> działać na dowolnym rodzaju T wraz z jedną klasą, która implementuje ją dla typów wartości, a drugą implementuje ją dla typów klas. Miej statyczną klasę ThingUsers<T> ze statycznym polem TheUser typu IThingUser<T> i poproś to pole o wystąpienie jednej z powyższych klas, a następnie ThingUsers<T>.theUser będzie w stanie zadziałać na dowolnym rodzaju 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"); 
    } 
} 

Jest to niezbędne do korzystania z refleksji do utworzenia instancji StructUser<T> lub ClassUser<T> jeśli kompilator nie wie, że T spełnia niezbędnych ograniczeń, ale nie jest to zbyt trudne.Po raz pierwszy ActOnThing<T>() jest używany dla określonego T, ThingUsers<T>.TheUser will be set to an instance which can be used directly for any future calls to ActOnThing(), więc wydajność powinna być bardzo dobra.

Zauważ, że jeśli dali Nullable<T>, metoda tworzy StructUser<T> i rzuca go do IThingUser<Nullable<T>>, zamiast próbować stworzyć sometype<Nullable<T>>, od pustych typów sami nie spełniają żadnych ograniczeń.

1

Jeśli nie potrzebujesz ogólnych parametrów i chcesz rozróżnić te przypadki w czasie kompilacji, możesz użyć następującego kodu.

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