【问题标题】:Method overloading based on generic constraints?基于泛型约束的方法重载?
【发布时间】:2019-07-28 10:43:57
【问题描述】:

我能否以某种方式拥有仅因泛型类型约束而不同的重载方法?

这不会编译:

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

    }

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

    }

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

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

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

【问题讨论】:

  • 这是一个很好的问题,希望@EricLippert 能回答。
  • @NikhilAgrawal:是的,我一直很喜欢 Jon Skeet 的回答,但这也是 Eric 的生计,所以当人们说“为什么不 C#允许吗?”
  • @Mike:很明确,因为 Eric 将作为 C# 编译器团队的主要开发人员提供内部观点。
  • @NikhilAgrawal:完全正确!

标签: c# generics


【解决方案1】:

我能否以某种方式拥有仅因泛型类型约束而不同的重载方法?

没有。就重载而言,它不是方法签名的一部分,就像返回类型不是一样。

在某些情况下有horrible 的“伪重载”方式,但我不建议走这条路。

有关更多信息,您可能需要阅读:

【讨论】:

  • 您在 horrible 链接下链接的博客文章 正是 我想要实现的,类的方法重载、值和可空值同名。
  • @BorisB.,请注意,如果 Jon 说它可怕,请不要实施它!
  • 通过定义一个带有结构约束的虚拟接口,并将其用作虚拟参数而不是T?,可以稍微改进“可怕”的代码。不像例如Drawing.Rectangle? 需要 20 个字节,接口只需要 4 个。至于它是“可怕的”,它将讨厌的东西限制在一个小的帮助方法中,并且在大多数情况下,性能损失应该不会太糟糕。
  • @JonSkeet:我和你一样讨厌传递一个虚拟参数,但是在无论参数类型如何(例如bool TryConvert&lt;T&gt;(o as Object, out T result))具有编译器自动选择的方法的语义应该相同的情况下在仅结构、仅类和通用方法中,似乎比要求调用代码使用不同的名称更干净(特别是因为如果使用较慢的通用方法而可以推断出较快的通用方法,则不会进行诊断)。跨度>
  • @JonSkeet 至少在 CLR 级别,返回类型是方法签名的一部分。如果我没记错的话,CLR 仅支持按返回类型进行重载,而 F# 使用它。
【解决方案2】:

这是不可能的。

出于重载目的,一般约束不被视为方法签名的一部分。

如果你想同时允许值类型和引用类型,为什么要限制呢?

【讨论】:

  • 为了避免跳过if (typeof(T)... 的圈套(可以/应该静态解决,并优化掉)。此外,它不仅仅是类/结构组合,它也是 where T:Foo/where T:Bar
  • 方法可用于引用类型或值类型的事实并不意味着在任何一种情况下都应使用相同的方法。举个简单的例子,假设有人想写一个方法bool TryCasting&lt;T&gt;(object it, out T result);。如果T是类类型,方法可以试试it as T;如果是值类型,it as T?。如果T 不能以任何一种方式推断出来,则可能必须使用反射来选择一种方法,但如果它可以在编译时推断出来,那么这样做会提高性能。
【解决方案3】:

更新。在 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()); 
}

【讨论】:

  • 排序...如果有包含不满足的通用约束的方法组,则将其删除。它仍然不允许 OP 在问题中编写代码,但约束仍然不是签名的一部分。换句话说,问题中的代码仍然无法在 C# 7.3 中编译。它在这里有效,因为方法签名不同。因此,虽然您的回答是真实的,但它不是这个问题的好答案,因为问题围绕着声明,而不是重载决议。
【解决方案4】:
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
}

【讨论】:

  • 这并没有回答所提出的问题,因为它需要引入新类型,并且需要签名实际上是不同的方法。这里提出的问题涉及具有相同签名但具有不同泛型类型约束的方法。
猜你喜欢
  • 1970-01-01
  • 2012-02-19
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多