问题
I've got an interface with some generic methods, and I wanted to implement a method with overloads to either accept an instance of a class, or its PK value (which is either an int or GUID but does vary).
I added to methods similar to these examples:
void DoSomething<TKey>(TKey key) where TKey: struct;
void DoSomething<TModel>(TModel model) where TModel : class;
The 'DoSomething' method name on the second of these is highlighted, and the error is
Type 'ISomeStuff' already defines a member called 'DoSomething' with the same parameter types.
I'm surprised by this as I've clearly defined by parameters to be of different type: one is a class and the other a struct.
Why isn't this sufficient to make the signatures different?
回答1:
Jon Skeet has an answer to everything: click me
quote:
the declarations only differ in generic constraints, and constraints aren't part of the signature
回答2:
Is possible to do it, you need create something like enable_if
from 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);
}
It can be expanded to use any disjoint where
to distinguish between overloads.
Only drawback is that It cant be used inside another generic method:
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
}
edit
But there is possibility of use dynamic
in that case to work around this limitation:
public static void Z1<T>(T t)
{
Func((dynamic)t); //if `T == int` it will call "struct" version
}
Only drawback is run time cost similar to call to Dictionary<,>
index.
回答3:
If one wishes to invoke a member generically regardless of whether it has a class constraint or a struct constraint, and have it invoke a method with a suitable constraint, one may define an interface IThingUser<T>
to act upon any type T
, along with one a class which implements it for value types and another which implements it for class types. Have a static class ThingUsers<T>
with a static field TheUser
of type IThingUser<T>
, and have it populate that field with an instance of one of the above classes, and then ThingUsers<T>.theUser
will be able to act upon any sort of 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");
}
}
It's necessary to use Reflection to create an instance of StructUser<T>
or ClassUser<T>
if the compiler doesn't know that T
satisfies the necessary constraint, but it's not too hard. After the first time ActOnThing<T>()
is used for a particular T
, ThingUsers<T>.TheUser will be set to an instance which can be used directly for any future calls to
ActOnThing(), so performance should be very good.
Note that if given a Nullable<T>
, the method creates a StructUser<T>
and casts it to IThingUser<Nullable<T>>
, rather than trying to create a sometype<Nullable<T>>
, since nullable types themselves don't satisfy any constraint.
回答4:
If you don't need generic parameters and just want to differentiate between these cases at compile time, you can use following code.
void Foo(object a) { } // reference type
void Foo<T>(T? a) where T : struct { } // nullable
void Foo(ValueType a) { } // value type
来源:https://stackoverflow.com/questions/7764911/generic-contraints-on-method-overloads