为什么我们无法将非泛型扩展方法添加到通用对象中?

时间:2015-05-01 12:30:42

标签: c# generics extension-methods

我终于厌倦了IEnumerable没有Add方法,并决定通过扩展方法添加我自己的方法。我最初的尝试是这样的:

public static void Add(this IEnumerable<T> items, T item)
{
    ...
}

这引发了关于T未定义的预期编译器错误,因此我将签名更改为Add<T>以定义它。 (有关说明,请参阅this answer。)

然而,这让我想到了。如果我们自己创建一个泛型类(比如IEnumerable<T>),我们可以像我最初尝试的那样添加方法,因为在类中定义了T

我知道扩展方法不是作为类的一部分创建的。没有&#34;魔法&#34;在编译器中发生,将它们添加到原始类中。

我仍然认为,由于初始参数的this声明,该参数中的<T>可用于定义方法的类型。

我的问题是:

  

为什么扩展方法存在这种限制?是否有针对此限制的解释?是否可以向语言团队提出并在未来的版本中添加?

更具体地说,Jonsey更有说服力地重申了我的观点:

  

我想我得到了你所要求的。为什么编译器不够智能,在给定方法签名的情况下,已经声明了T,并且确实不需要在签名中?

修改

我应该在发布之前使用我的新方法(Add<T>),因为我发现在使用该方法时,我不必一般地调用它,我可以使用.Add()。我想这与this answer一致。我仍然觉得它必须被宣布的方式很奇怪,也许这会给整个情况带来麻烦。

反对this question 重复的论点 提及创建IEnumerable<T>.Add()仅仅是为了说明我找到这个&#34;特殊性&#34;背后的原因,而我的问题更通用,而不是特定于那个方法。

4 个答案:

答案 0 :(得分:9)

嗯,你需要问问自己这里T有什么特别之处。假设我们要写:

public static void Add(this IEnumerable<X> items, X item)

......你希望它能起作用吗?如果是这样,请考虑:

public static void Add(this IEnumerable<Button> items, Button item)

你是否希望这意味着同样的事情,即有效地采用任何序列的通用方法,或者实际上只对IEnumerable<Button>有意义,其中ButtonSystem.Windows.Forms.Button类或任何适当的using指令表示什么?

基本上,编译器需要查找T的含义。那可以是:

  • 声明类
  • 中的类型参数
  • 声明方法中的类型参数
  • 普通类型查找

在您的情况下,您处于非泛型类和非泛型方法中,因此它会回退到正常类型查找失败。

基本上你希望这是一个类型参数,所以它必须是泛型方法(或类,但泛型类中不允许使用扩展方法)。

如果你认为第一个例子(X)应该有用,那么大概是你期望T在上下文中被查找IEnumerable<>类型,这使得事情变得更奇怪,因为它意味着类型参数名称从“客户端”代码变得重要,因为它们不是语言中的任何其他地方。它还使其他事情变得棘手。例如:

static void Add(this IEnumerable<T> items, Dictionary<T, T> dict)

此处TIEnumerable<T>有效,但Dictionary<,>TKeyTValue ...所以编译器应该只说IEnumerable<T>胜利?它最终只是很多比仅仅说“不,如果你想要一个通用方法,你必须声明一个类型参数。”

答案 1 :(得分:3)

你可以,你只需要告诉编译器什么是T,因为它在扩展方法的上下文中不理解它。

public static void MyAdd<T>(this IList<T> items, T item)
{

}

使用它时,它会从项目中推断出类型,例如:

List<string> myList = new List<string>();
myList.MyAdd("this is added to the string");

在Intellisense中看起来有点奇怪,因为它包含&lt;&gt;在名称之后,但您不必使用它,它将自己推断出类型。

正如其他评论所说,IEnumerable<T>是一个界面,而不是一个类,你不能Add到一个IEnumerable,所以你的原始帖子没有意义将“Add”扩展方法添加到不是真正集合的内容中。但是如果您使用的是IList之类的集合,那么它的工作正常。

Intellisense View

答案 2 :(得分:1)

  

我仍然认为,由于初始参数的这个声明,该参数可用于定义方法的类型。

为什么?

假设你有这种方法:

public void Foo(int i1, T item)
{
}

编译器是否应自动检测T并将其转换为:

public void Foo<T>(int i1, T item)
{
}

让我们说你有

public void Foo(int i1, T1 item1, T2 item2)
{
}

编译器应该将其翻译成?

public void Foo<T1, T2>(int i1, T1 item1, T2 item2)
{
}

public void Foo<T2, T1>(int i1, T1 item1, T2 item2)
{
}

现在,使用扩展方法是同样的问题。没有理由认为T“这个”通用论证应该是第一个......

public static void Foo<T2, T1>(this IEnumerable<T1> enu, T2 somethingelse)
{
}

这只是惯例

答案 3 :(得分:1)

目前,编译器在static类内部编译static方法并观察到第一个参数前面有this时,所有编译器必须执行的操作是它必须在此方法上发出ExtensionAttribute。否则,它继续以与任何其他(非扩展)static方法完全相同的方式解析/编译此方法。

根据您的提议,它必须从根本上改变它在一个特定位置的解析规则,并使语言 less 保持一致。我不会将其描述为限制,而是将语言功能如何协同工作的逻辑结果