使用值类型传递时,编译器不会调用适当的泛型重载

时间:2013-02-07 11:18:45

标签: c# generics type-inference overloading value-type

我有这样的公共功能:

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时,它无法正确调用struct重载吗?另外:

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 :(

我只留下重命名这两个函数吗?我认为有一个统一的名称是合适的,这样调用者就不必担心实现细节,只需调用{{1}对于任何类型。

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

Get

但还有更多魔力:(:

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;
}

因此,如果在Marc所说的约束检查之前出现过载分辨率,那么我这次也不应该得到相同的错误吗?但不是。为什么会这样?到底是怎么回事? :X

3 个答案:

答案 0 :(得分:6)

在重载之后完成约束检查;重载决议似乎更喜欢第一个版本。但是你可以强迫它使用另一个:

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

甚至:

mango.Get((int?)0);

就个人而言,我可能只是更改名称以避免含糊不清。

答案 1 :(得分:1)

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

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

我真的发现总是无法考虑过载评估中的约束,因为可以为Nullable<T>[]参数指定默认空值,并假装参数不存在。但是,vb.net编译器和C#编译器似乎有所不同,当涉及到他们认为含糊不清的内容以及他们接受的内容时。

答案 2 :(得分:0)

让我试着回答一下自己。

正如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>的调用失败,因为int不是class

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

mango.Get(0);

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

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

它工作,调用正确的重载。这也是第二个例子中发生的事情。它的工作原理是因为我提供了正确的参数。

我打电话的时候:

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

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

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

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

我很好,因为编译器不会自动执行转换,因为对于每种可能的类型都会计算得太多,但是C#中的缺陷似乎是约束这一事实检查不涉及重载决策。即使我提供类似mango.Get<int>()mango.Get<int>(0)的类型,重载解析也不会更喜欢struct版本并使用{{ 1}}用于参数default(int?)。对我来说很奇怪。