基于泛型约束的方法重载?

18

我能否通过泛型约束来区分仅在泛型类型约束上有所不同的重载方法?

以下代码无法编译:

    void Foo<T>(T bar) where T : class
    {

    }

    void Foo<T>(T bar) where T : struct
    {

    }

由于这些是“开放”的方法,当在代码中使用具体类型T引用时,实际方法应该被关闭/构造/完全定义,然后就可以清楚地调用哪个重载。

显而易见的解决方案是不要重载它们,但我想知道为什么这在C#中行不通?

附加问题:如果这只是C#编译器的限制,那么IL是否允许这样的重载?


2
这是一个很好的问题,希望@EricLippert能够回答。 - Mike Perrenoud
1
@NikhilAgrawal:是的,我总是喜欢Jon Skeet的回答,但这也是Eric的专长,所以当人们问“为什么C#不允许这样做?”时,看他的回答总是很有趣的。 - Mike Perrenoud
1
@Mike:绝对是的,因为Eric会从C#编译器团队的主要开发者角度给我们提供内部视角。 - Nikhil Agrawal
@NikhilAgrawal:没错! - Mike Perrenoud
4个回答

11

1
你在那个糟糕链接下面链接的博客文章 恰好是我想要实现的内容,即为一个类添加方法重载,值和可空类型名称相同。 - Boris B.
1
@BorisB.,需要注意的是,如果Jon认为它很糟糕,请不要实现它! - Mike Perrenoud
“可怕”的代码可以通过定义一个带有结构约束的虚拟接口来进行改进,然后将其用作虚拟参数而不是 T?。与例如 Drawing.Rectangle? 不同,它需要 20 字节,该接口仅需要四个字节。至于它为什么“可怕”,它将恶劣性限制在小型辅助方法中,在大多数情况下,性能损失不应太严重。 - supercat
@supercat:我认为,这种方法仍然涉及到为了签名区分而专门添加一个参数的重载方式,这对我来说是可怕的。在我看来,重命名方法是更好的选择。 - Jon Skeet
2
@JonSkeet:我和你一样不喜欢传递虚拟参数,但在方法的语义应该与参数类型无关的情况下(例如 bool TryConvert<T>(o as Object, out T result)),让编译器自动选择仅结构体、仅类或通用方法似乎比要求调用代码使用不同名称更清晰(特别是因为如果在可以推断出更快的方法的情况下使用了较慢的通用方法,则不会有诊断)。 - supercat
1
@JonSkeet 至少在 CLR 级别,返回类型是方法签名的一部分。CLR 仅支持按返回类型重载,而 F# 使用它,如果我没有记错的话。 - Pavel Voronin

5

这是不可能的。

泛型约束不被视为方法签名的一部分,不能用于重载。

如果您想允许值类型和引用类型,为什么还要进行约束呢?


2
为了避免跳过 if (typeof(T)... 的繁琐步骤(顺便说一下,这应该在静态解析时得到解决并优化掉)。此外,它不仅仅是类/结构体组合,还包括 where T:Foo/where T:Bar - Boris B.
1
一个方法既可以用于引用类型,也可以用于值类型,并不意味着在任何情况下都应该使用相同的方法。举个简单的例子,假设我们想编写一个方法 bool TryCasting<T>(object it, out T result);。如果 T 是类类型,则该方法可以尝试 it as T;如果是值类型,则为 it as T?。如果无法推断出 T 的类型,则可能需要使用反射来选择方法,但如果可以在编译时推断出,这样做将提高性能。 - supercat

0

更新。在 C# 7.3 中,泛型约束现在是重载决策的一部分。

因此,这段代码将被编译:

class Animal { } 
class Mammal : Animal { } 
class Giraffe : Mammal { }
class Reptile : Animal { } 

static void Foo<T>(T t) where T : Reptile { }
static void Foo(Animal animal) { }
static void Main() 
{ 
    Foo(new Giraffe()); 
}

1
有点像... 如果存在包含未满足泛型约束的方法组,则这些约束将被移除。但它仍然不允许OP在问题中编写代码,因为这些约束仍然不是签名的一部分。换句话说,在C# 7.3中,问题中的代码仍然无法编译。它在这里工作是因为方法签名不同。因此,虽然您的答案是事实正确的,但它不是这个问题的好答案,因为问题围绕声明而不是重载解析。 - Lasse V. Karlsen

-1
struct _Val_Trait<T> where T:struct { }
struct _Ref_Trait<T> where T:class { }

static void Foo<T>(T bar, _Ref_Trait<T> _ = default(_Ref_Trait<T>)) where T:class
{
    Console.WriteLine("ref");
}

static void Foo<T>(T bar, _Val_Trait<T> _ = default(_Val_Trait<T>)) where T:struct
{
    Console.WriteLine("val");
}

static void Main() 
{
    Foo(1);            // -->"val"
    Foo(DateTime.Now); // -->"val"
    Foo("");           // -->"ref"

    //but:
    //Foo(null); - error: type cannot be inferred
}

这并没有回答所提出的问题,因为它需要引入新类型,并需要方法签名实际上是不同的。这里所问的问题涉及到具有相同签名但具有不同泛型类型约束的方法。 - Peter Duniho

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接