方法重载的通用约束
本文关键字:约束 重载 方法 | 更新日期: 2023-09-27 18:11:10
我有一些泛型方法的接口,我想实现一个重载的方法,要么接受一个类的实例,要么接受它的PK值(要么是int,要么是GUID,但确实不同)。
我添加了类似这些例子的方法:
void DoSomething<TKey>(TKey key) where TKey: struct;
void DoSomething<TModel>(TModel model) where TModel : class;
第二个的'DoSomething'方法名被高亮显示,错误是
Type 'ISomeStuff'已经定义了一个名为'DoSomething'的成员参数类型相同
我对此感到惊讶,因为我已经明确地通过参数定义了不同的类型:一个是类,另一个是结构。
为什么这不足以使签名不同?
要做到这一点,您需要从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<,>
索引。
Jon Skeet有一个答案:点击我
:
声明仅在泛型约束和约束中不同不是签名的一部分
如果希望通用地调用成员,而不管它是否具有类约束或结构约束,并让它调用具有适当约束的方法,则可以定义一个接口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");
}
}
如果编译器不知道T
满足必要的约束,则有必要使用反射来创建StructUser<T>
或ClassUser<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>>
,因为可空类型本身不满足任何约束。
如果您不需要泛型参数,只是想在编译时区分这些情况,您可以使用以下代码:
void Foo(object a) { } // reference type
void Foo<T>(T? a) where T : struct { } // nullable
void Foo(ValueType a) { } // value type