通用约束,其中 T : struct 和 where T : class
Posted
技术标签:
【中文标题】通用约束,其中 T : struct 和 where T : class【英文标题】:Generic constraints, where T : struct and where T : class 【发布时间】:2011-02-27 19:20:55 【问题描述】:我想区分以下情况:
-
普通值类型(例如
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
但是,如果我尝试像这样检测案例 (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)
进行编译,将其映射到上述函数之一(或第三个具有另一个约束的函数,我没有想到)?
【问题讨论】:
对于引用类型有:new(),但是对于可为空的值类型,这有奇怪的行为。 【参考方案1】:约束不是签名的一部分,但参数是。并且在重载决议期间强制执行参数中的约束。
所以让我们把约束放在一个参数中。这很丑陋,但它有效。
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
(迟到六年总比没有好?)
【讨论】:
哈,好主意!实际上,您不需要将ignore
参数添加到第二个采用T?
的Foo<T>
函数中。
这让我有机会在code.fitness/post/2016/04/generic-type-resolution.html上发表关于该主题的博客
我的想法来自one of Eric Lippert's blog posts。我一直喜欢恶作剧。至于T?,我需要它的情况只有案例1和3,我忘了测试是否需要。
这很光滑。我喜欢使用“_”而不是“忽略”函数式编程。
更简单的方式,不需要辅助类:不确定这是否只是较新的语言版本或其他。我想这可能会导致结构的额外分配,但无论如何我都需要测试是否等于默认值。 static void Foo<T>(T? value) where T : struct
static void Foo<T>(T value, T defaultValue = default) where T : struct
static void Foo<T>(T obj) where T : class
【参考方案2】:
很遗憾,您无法仅根据约束来区分要调用的方法类型。
因此,您需要在不同的类中定义一个方法,或者使用不同的名称。
【讨论】:
+1。当然,第一个和第二个工作,因为T
和T?
是不同的论点。 (T
和 Nullable<T>
)
+1 见:blogs.msdn.com/b/ericlippert/archive/2009/12/10/…
感谢您的快速回复;如果我无法区分类型,有没有办法通过放松一些约束来编译我的最后一个示例?
啊,只需删除方法 (1) 的 where T : struct
,我的示例就会编译。这对我来说已经足够了。
实际上,如果不介意泛型引用类型的虚拟“可选”参数对其泛型参数有约束,则可以根据约束来区分方法调用的类型,并具有该参数的“null”默认值。由于编译器将排除任何类型无法构造的重载,因此虚拟参数类型中的约束将有效限制考虑的重载。当然,如果编译器可以在不为虚拟参数提供值的调用站点上执行此操作,它...【参考方案3】:
除了你对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>
。)
【讨论】:
可以通过使用类似于Comparer<T>.Default
的方法来改进事情,例如使用Action<T>
类型的公共字段FooMethod
创建一个私有静态泛型类FooInvoker<T>
(因为FooInvoker<T>
在MyClass
之外无法访问,就不会有外部代码滥用公共字段的风险)?如果FooInvoker<T>
的类构造函数适当地设置FooMethod
,我认为这可能会避免在运行时查找字典的需要(我不知道.net 是否需要在每次Foo<T>
是称为)。
查看我发布的答案,了解如何使用静态类。我可能犯了一些语法错误,因为我是从记忆中输入的(主要是 vb.net 中的程序),但应该有足够的大纲让你继续前进。【参考方案4】:
删除第一个方法的结构约束。如果您需要区分值类型和类,可以使用参数的类型来做到这一点。
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
【讨论】:
@Maxim:谢谢。我面临的问题是,在不可为空的方法中,我必须能够调用其他接受并返回T?
的函数,而如果没有where T : struct
约束,这是无效的。【参考方案5】:
放大我对 LukeH 的评论,如果需要使用反射来调用基于类型参数的不同操作(与对象实例的类型不同),一种有用的模式是创建一个私有的通用静态类,类似于以下(这个确切的代码未经测试,但我以前做过这种事情):
静态类 FooInvoker请注意,如果在 TT
不符合该方法的约束时尝试为 ActionForOneKindOfThing<TT>(TT param)
创建委托,反射将引发异常。因为系统在创建委托时验证了TT
的类型,所以可以安全地调用theAction
而无需进一步的类型检查。另请注意,如果外部代码这样做:
只有第一次调用需要任何反射。后续调用将直接调用委托。
【讨论】:
【参考方案6】:谢天谢地,从 C# 版本 7.3 开始,这种乱七八糟的事情就更少了
参见Whats new in C# 7.3 - 它不是很明确,但它现在似乎在重载解析期间在某种程度上使用了“where”参数。
重载解析现在有更少的模棱两可的情况
另请参阅您的 Visual Studio 项目中的 Selecting C# Version
它仍然会与以下内容发生冲突
Foo(x);
...
static void Foo<T>(T a) where T : class // 3
static void Foo<T>(T a) where T : struct // 3
但会正确解决
Foo(x);
...
static void Foo<T>(T a, bool b = false) where T : class // 3
static void Foo<T>(T a) where T : struct // 3
【讨论】:
我尝试了 C# 7.3,它并没有改变我原来问题中方法 (1) 和 (3) 之间的冲突。我仍然收到错误类型'X'已经定义了一个名为'Foo'的成员具有相同的参数类型。 @PierreArnaud 似乎我有点过分了。我的情况略有不同,因此我认为它会是您的情况。我已经修改了回复以反映这一点......似乎MS已经改进了这一点,但他们还有一些工作要做......【参考方案7】:如果您不需要泛型参数并且只想在编译时区分这三种情况,您可以使用以下代码。
static void Foo(object a) // reference type
static void Foo<T>(T? a) where T : struct // nullable
static void Foo(ValueType a) // valuetype
【讨论】:
以上是关于通用约束,其中 T : struct 和 where T : class的主要内容,如果未能解决你的问题,请参考以下文章
`<T extends E>` 形式的通用约束,其中`E` 是`enum`?