[英]Generic contraints on method overloads
我有一個帶有一些通用方法的接口,我想實現一個帶有重載的方法,以接受類的實例或其 PK 值(它可以是 int 或 GUID,但確實有所不同)。
我添加了類似於這些示例的方法:
void DoSomething<TKey>(TKey key) where TKey: struct;
void DoSomething<TModel>(TModel model) where TModel : class;
其中第二個的“DoSomething”方法名稱突出顯示,錯誤是
類型“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<,>
索引。
如果希望通用地調用一個成員,而不管它是否具有類約束或結構約束,並讓它調用具有合適約束的方法,則可以定義一個接口IThingUser<T>
來作用於任何類型T
,以及一個類為值類型實現它,另一個類為類類型實現它。 有一個靜態類ThingUsers<T>
具有靜態字段TheUser
類型的IThingUser<T>
並且具有它填充該字段與上述類別之一的一個實例,然后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() 的ThingUsers<T>.TheUser will be set to an instance which can be used directly for any future calls to
,因此性能應該非常好。
請注意,如果給定Nullable<T>
,該方法會創建一個StructUser<T>
並將其轉換為IThingUser<Nullable<T>>
,而不是嘗試創建sometype<Nullable<T>>
,因為可空類型本身不會t 滿足任何約束。
如果您不需要泛型參數並且只想在編譯時區分這些情況,您可以使用以下代碼。
void Foo(object a) { } // reference type
void Foo<T>(T? a) where T : struct { } // nullable
void Foo(ValueType a) { } // value type
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.