编译器在使用值类型传递时未调用适当的泛型重载

本文关键字:调用 重载 泛型 类型 编译器 | 更新日期: 2023-09-27 17:58:13

我有这样的公共函数:

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?)

这里真正的歧义是什么?当T int时,它不能适当地调用结构重载吗?也:

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

编译器在使用值类型传递时未调用适当的泛型重载

约束检查是在重载解析IIRC之后完成的;重载解析似乎更喜欢第一个版本。不过,您可以强制它使用另一个:

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

甚至:

mango.Get((int?)0);

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

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

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

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

让我试着回答自己。

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

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>,因为intclass

提供默认参数时,情况会有所变化。如果我打电话

mango.Get(0);

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

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

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

当我打电话时:

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

众所周知,objg的,因此T等于Guid。但是如果我打电话

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

这是行不通的,因为g现在是Guid而不是Guid?,编译器不会自动执行转换,而是必须明确地告诉编译器。

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