9

我有这样的公共职能:

public static T Get<T>(this Mango m, T defaultValue = default(T)) where T : class
{
    //do something; return something;
}

public static T? Get<T>(this Mango m, T? defaultValue = default(T?)) where T : struct
{
    //do something; return something;
}

基本上我想单独处理引用类型和可为空的类型。它编译;直到我要求值类型。对于引用类型,它会编译。

mango.Get<string>(); // compiles..
mango.Get(""); // compiles..

mango.Get<int>(); // The type 'int' must be a reference type in order to use it as 
                  // parameter 'T' in the generic type or method Get<T>(Mango, T)
//also            // The call is ambiguous between the following methods or properties: 
                  // Get<int>(Mango, int) and Get<int>(Mango, int?)

这里有什么真正的歧义?什么时候,Tint不能适当地调用结构重载吗?还:

mango.Get<int>(0);  // The type 'int' must be a reference type in order to use it as 
                    // parameter 'T' in the generic type or method Get<T>(Mango, T)

为什么编译器只检测引用类型重载?我尝试了两个单独的重载:

public static T Get<T>(this Mango m) where T : class
{
    return default(T);
}

public static T? Get<T>(this Mango m) where T : struct
{
    return default(T);
}

public static T Get<T>(this Mango m, T def) where T : class
{
    return default(T);
}

public static T? Get<T>(this Mango m, T? def) where T : struct
{
    return default(T);
}

问题仍然存在。显然,前两种方法在这里不能编译,因为重载不仅仅基于约束。

我尝试通过删除受class约束的重载并仅保留受约束的重载struct,如下所示:

public static T? Get<T>(this Mango m, T? defaultValue = default(T?)) where T : struct
{
    //do something; return something;
}

mango.Get<int>(); // voila compiles!
mango.Get<int>(0); // no problem at all..
// but now I can't have mango.Get<string>() for instance :(

我只剩下重命名这两个函数了吗?我觉得有一个统一的名字是合适的,这样调用者就不必担心实现细节,而只需调用Get任何类型。

更新:如果我必须避免使用可选参数,Marc 的解决方案将不起作用。

mango.Get<int>(); // still wouldnt work!!

但还有更多的魔力:(:(

public static bool IsIt<T>(this T? obj) where T : struct
{
    return who knows;
}

public static bool IsIt<T>(this T obj) where T : class
{
    return perhaps;
}

无论如何,我期望相同的编译器错误(根据我)来惹恼我。但是这次不行。

Guid? g = null;
g.IsIt(); //just fine, and calls the struct constrained overload
"abcd".IsIt(); //just fine, and calls the class constrained overload

因此,如果像 Marc 所说的那样,如果重载解决方案在约束检查之前出现,那么这次我不应该得到同样的错误吗?但不是。为什么会这样??这到底是怎么回事?:X

4

3 回答 3

6

在重载决议 IIRC之后进行约束检查;重载决议似乎更喜欢第一个版本。但是,您可以强制它使用另一个:

mango.Get<int>((int?)0);

甚至:

mango.Get((int?)0);

就个人而言,我可能只是更改名称以避免歧义。

于 2013-02-07T11:23:12.460 回答
1

有趣的是,编译器将检查在方法签名中使用的泛型类型中指定的约束,但不会检查签名本身中的约束。

因此,如果一个方法接受两个参数,一个是 typeT where T : struct和 a Nullable<T>[],编译器将不会考虑任何T不是结构的方法。在评估重载时不考虑方法的指定struct约束,但约束到结构T的事实是。Nullable<T>T

我真的发现在重载评估中完全无法考虑约束是很奇怪的,因为可以为参数指定一个默认的空值Nullable<T>[],并假装该参数不存在。但是,vb.net 编译器和 C# 编译器在它们认为模棱两可的内容和接受的内容方面似乎有所不同。

于 2013-02-10T23:31:30.460 回答
0

让我试着回答自己。

正如马克所说,约束检查是在重载决议之后完成的,并且在

public static T Get<T>(this Mango m, T defaultValue = default(T)) where T : class
{
    //do something; return something;
}

public static T? Get<T>(this Mango m, T? defaultValue = default(T?)) where T : struct
{
    //do something; return something;
}

重载决议更喜欢class版本。但这只是当编译器在两个相似的重载之间进行选择时(没有可选参数,在这种情况下两个重载都变得相同,忽略约束)。现在,当应用约束时,调用失败,Get<int>因为intis not class

当提供默认参数时,情况会发生一些变化。如果我打电话

mango.Get(0);

编译器有足够的能力调用正确的重载,但现在哪个重载接受intT where T: struct?没有一个。在给定的示例中,第二个参数应该是T?and not T编译器不会通过对每个参数类型应用所有可用的强制转换来自动解决重载问题。这不是一个错误,但那少了一个功能,仅此而已。如果我这样做:

int? i = 0;
mango.Get(i);

它起作用了,调用了正确的重载。这也是第二个示例中发生的情况。它之所以有效,是因为我提供了正确的参数。

当我打电话时:

Guid? g = null;
g.IsIt();

obj已知是g,因此T等于Guid。但如果我打电话

Guid g = Guid.NewGuid();
g.IsIt();

这不起作用,因为gis nowGuid和 notGuid?并且编译器不会自动进行转换,而是必须明确告诉编译器。

我对编译器不会自动进行强制转换这一事实感到满意,因为对于每种可能的类型来说计算量太大了,但 C# 中似乎存在的一个缺陷是约束检查不涉及重载这一事实解析度。即使我提供了类似的类型,mango.Get<int>()或者mango.Get<int>(0)重载决议不会更喜欢struct版本并使用default(int?)for argument defaultValue。在我看来很奇怪。

于 2013-02-10T12:36:18.073 回答