我想区分以下情况:
int
)int?
)string
) - 可选地,我不在乎这是否映射到上面的(1)或(2)我提出了以下代码,适用于案例(1)和(2):
static void Foo<T>(T a) where T : struct { } // 1
static void Foo<T>(T? a) where T : struct { } // 2
但是,如果我尝试像这样检测case(3),它就不会编译:
static void Foo<T>(T a) where T : class { } // 3
错误消息是类型'X'已经定义了一个名为'Foo'的成员,其参数类型相同。好吧,不知怎的,我无法区分where T : struct
和where T : class
。
如果删除第三个函数(3),则以下代码不会编译:
int x = 1;
int? y = 2;
string z = "a";
Foo (x); // OK, calls (1)
Foo (y); // OK, calls (2)
Foo (z); // error: the type 'string' must be a non-nullable value type ...
如何让Foo(z)
进行编译,将其映射到上述函数之一(或者是另一个具有另一个约束的函数,我没有想过)?
答案 0 :(得分:48)
约束不是签名的一部分,但参数是。在重载解析期间强制执行参数约束。
因此,让我们将约束放在一个参数中。这很难看,但它确实有效。
class RequireStruct<T> where T : struct { }
class RequireClass<T> where T : class { }
static void Foo<T>(T a, RequireStruct<T> ignore = null) where T : struct { } // 1
static void Foo<T>(T? a) where T : struct { } // 2
static void Foo<T>(T a, RequireClass<T> ignore = null) where T : class { } // 3
(比从来没有晚了六年?)
答案 1 :(得分:19)
不幸的是,您无法仅根据约束来区分要调用的方法类型。
因此,您需要在不同的类中定义方法或使用不同的名称来定义方法。
答案 2 :(得分:10)
继续对Marnix's answer发表评论后,您可以通过一些反思来实现您的目标。
在下面的示例中,无约束Foo<T>
方法使用反射来调用对相应约束方法的调用 - FooWithStruct<T>
或FooWithClass<T>
。出于性能原因,我们将创建并缓存强类型委托,而不是每次调用Foo<T>
方法时使用纯反射。
int x = 42;
MyClass.Foo(x); // displays "Non-Nullable Struct"
int? y = 123;
MyClass.Foo(y); // displays "Nullable Struct"
string z = "Test";
MyClass.Foo(z); // displays "Class"
// ...
public static class MyClass
{
public static void Foo<T>(T? a) where T : struct
{
Console.WriteLine("Nullable Struct");
}
public static void Foo<T>(T a)
{
Type t = typeof(T);
Delegate action;
if (!FooDelegateCache.TryGetValue(t, out action))
{
MethodInfo mi = t.IsValueType ? FooWithStructInfo : FooWithClassInfo;
action = Delegate.CreateDelegate(typeof(Action<T>), mi.MakeGenericMethod(t));
FooDelegateCache.Add(t, action);
}
((Action<T>)action)(a);
}
private static void FooWithStruct<T>(T a) where T : struct
{
Console.WriteLine("Non-Nullable Struct");
}
private static void FooWithClass<T>(T a) where T : class
{
Console.WriteLine("Class");
}
private static readonly MethodInfo FooWithStructInfo = typeof(MyClass).GetMethod("FooWithStruct", BindingFlags.NonPublic | BindingFlags.Static);
private static readonly MethodInfo FooWithClassInfo = typeof(MyClass).GetMethod("FooWithClass", BindingFlags.NonPublic | BindingFlags.Static);
private static readonly Dictionary<Type, Delegate> FooDelegateCache = new Dictionary<Type, Delegate>();
}
(注意这个例子不是线程安全的。如果你需要线程安全,那么你需要使用某种锁定围绕对缓存字典的所有访问,或者 - 如果你能够以.NET4为目标 - 改为使用ConcurrentDictionary<K,V>
。)
答案 3 :(得分:5)
删除第一个方法上的struct约束。如果需要区分值类型和类,可以使用参数类型来执行此操作。
static void Foo( T? a ) where T : struct
{
// nullable stuff here
}
static void Foo( T a )
{
if( a is ValueType )
{
// ValueType stuff here
}
else
{
// class stuff
}
}
答案 4 :(得分:2)
将我的评论放大到LukeH,如果需要使用Reflection来调用基于类型参数的不同动作(与对象实例的类型不同),那么这是一个有用的模式,就是创建一个私有的通用静态类,如以下(这个确切的代码未经测试,但我之前做过这样的事情):
static class FooInvoker<T> { public Action<Foo> theAction = configureAction; void ActionForOneKindOfThing<TT>(TT param) where TT:thatKindOfThing,T { ... } void ActionForAnotherKindOfThing<TT>(TT param) where TT:thatOtherKindOfThing,T { ... } void configureAction(T param) { ... Determine which kind of thing T is, and set `theAction` to one of the ... above methods. Then end with ... theAction(param); } }
请注意,当ActionForOneKindOfThing<TT>(TT param)
不符合该方法的约束时,如果有人试图为TT
创建委托,则Reflection会抛出异常。由于系统在创建委托时验证了TT
的类型,因此可以安全地调用theAction
而无需进一步进行类型检查。另请注意,如果外部代码执行:
FooInvoker<T>.theAction(param);
只有第一次通话才需要任何反射。后续调用将直接调用委托。
答案 5 :(得分:1)
如果您不需要通用参数,只想在编译时区分这三种情况,可以使用以下代码。
static void Foo(object a) { } // reference type
static void Foo<T>(T? a) where T : struct { } // nullable
static void Foo(ValueType a) { } // valuetype
答案 6 :(得分:0)
非常感谢C#7.3版不需要这种混乱的操作
请参见Whats new in C# 7.3-它不是很明确,但是现在看来在重载解析期间在某种程度上使用了“ where”参数。
超载解决方案现在具有较少的歧义情况
另请参见Visual Studio项目中的Selecting C# Version
它仍然会与以下内容冲突
password
但可以正确解决
Foo(x);
...
static void Foo<T>(T a) where T : class { } // 3
static void Foo<T>(T a) where T : struct { } // 3