为什么在通用静态类中声明扩展方法是不可能的?
我想为一些generics类创建很多扩展方法,例如for
public class SimpleLinkedList where T:IComparable
我已经开始创建这样的方法:
public static class LinkedListExtensions { public static T[] ToArray(this SimpleLinkedList simpleLinkedList) where T:IComparable { //// code } }
但是当我试图使LinkedListExtensions类像这样通用时:
public static class LinkedListExtensions where T:IComparable { public static T[] ToArray(this SimpleLinkedList simpleLinkedList) { ////code } }
我得到“扩展方法只能在非generics,非嵌套的静态类中声明”。
而我正在试图猜测这种限制来自哪里并且没有任何想法。
编辑:仍然没有明确的问题愿景。 似乎这只是因为某种原因没有实现。
一般来说,由于在使用扩展方法时没有指定类,编译器无法知道哪个是定义扩展方法的类:
static class GenStatic { static void ExtMeth(this Class c) {/*...*/} } Class c = new Class(); c.ExtMeth(); // Equivalent to GenStatic .ExtMeth(c); what is T?
由于扩展方法本身可以是通用的,因此这根本不是真正的问题:
static class NonGenStatic { static void GenExtMeth(this Class c) {/*...*/} } Class c = newClass(); c.ExtMeth(); // Equivalent to NonGenStatic.ExtMeth (c); OK
您可以轻松地重写您的示例,以便静态类不是通用的,但通用方法是。 实际上,这就是编写Enumerable
等.NET类的方法。
public static class LinkedListExtensions { public static T[] ToArray(this SimpleLinkedList where T:IComparable simpleLinkedList) { // code } }
问题是编译器如何进行扩展解析?
假设你定义了你描述的两种方法:
public static class LinkedListExtensions { public static T[] ToArray(this SimpleLinkedList simpleLinkedList) where T:IComparable { //// code } } public static class LinkedListExtensions where T:IComparable { public static T[] ToArray(this SimpleLinkedList simpleLinkedList) { ////code } }
在下列情况下使用哪种方法?
SimpleLinkedList data = new SimpleLinkedList (); int[] dataArray = data.ToArray();
我的猜测是语言设计者决定将扩展方法限制为非generics类型以避免这种情况。
不要认为扩展方法与包含它们的静态类相关联。 相反,将它们视为绑定到特定命名空间。 因此,定义它们的静态类只是用于在命名空间内声明这些方法的shell。 虽然您可以很好地为不同类型的扩展方法编写多个类,但您不应该将类本身视为一种明确分组扩展方法的方法。
扩展方法不扩展包含它们的类的任何属性。 该方法的签名将定义有关扩展方法的所有内容。 虽然你也可以这样调用你的方法, LinkedListExtensions.ToArray(...)
,但我不认为这是扩展方法的意图。 因此,我相信框架创建者可能创建了您遇到的限制,作为向开发人员通知扩展方法是自包含的并且不直接与它们所在的类相关联的方式。
一个非常有趣的问题,我从未想过使用静态generics类,但至少看起来似乎有可能。
在声明扩展方法的上下文中,您不仅可以声明某个generics类型的扩展方法(例如IEnumerable
),还可以将类型参数T
引入等式中。 如果我们同意将IEnumerable
和IEnumerable
视为不同的类型,这在概念层面也是有意义的。
能够在静态generics类中声明扩展方法可以避免一遍又一遍地重复类型参数约束,实际上将IEnumerable
所有扩展方法分组IEnumerable
在一起。
根据规范(需要引用),扩展方法只能在静态非嵌套和非generics类中声明。 前两个约束的原因相当明显:
- 可能不会带任何状态,因为它不是混合物,只是语法糖。
- 必须具有与其提供扩展名的类型相同的词法范围。
对非generics静态类的限制对我来说似乎有点武断,我不能在这里提出一个技术原因。 但可能是语言设计者决定不鼓励你编写依赖于你希望提供扩展方法的generics类的类型参数的扩展方法。 相反,他们希望您提供扩展方法的真正通用实现,但除了通用实现之外,还可以提供扩展方法的优化/专用(编译时绑定)版本。
让我想起了C ++中的模板专业化。 编辑:不幸的是这是错误的,请参阅下面的我的补充。
好的,因为这是一个非常有趣的话题,我做了一些进一步的研究。 实际上我在这里错过了技术限制。 我们来看一些代码:
public static class Test { public static void DoSomething(this IEnumerable source) { Console.WriteLine("general"); } public static void DoSomething (this IEnumerable source) where T :IMyInterface { Console.WriteLine("specific"); } }
这实际上会因此编译器错误而失败:
类型’ConsoleApplication1.Test’已经定义了一个名为’DoSomething’的成员,它具有相同的参数类型
好的,接下来我们尝试将其拆分为两个不同的扩展类:
public interface IMyInterface { } public class SomeType : IMyInterface {} public static class TestSpecific { public static void DoSomething(this IEnumerable source) where T : IMyInterface { Console.WriteLine("specific"); } } public static class TestGeneral { public static void DoSomething (this IEnumerable source) { Console.WriteLine("general"); } } class Program { static void Main(string[] args) { var general = new List(); var specific = new List(); general.DoSomething(); specific.DoSomething(); Console.ReadLine(); } }
根据我的初步印象(昨天晚上已经很晚),这将导致呼叫站点的模糊性。 要解决这种歧义,需要以传统方式调用扩展方法,但这违背了我们的意图。
所以这给我们留下了一种情况,即无法声明扩展方法的编译时绑定generics特化。 另一方面,我们仍然没有理由不能仅为单个特殊generics类型参数声明扩展方法。 因此,在静态generics类中声明它们会很好。
另一方面,编写一个扩展方法,如:
public static void DoSomething(this IEnumerable source) where T : IMyInterface {}
要么
public static void DoSomething(this IEnumerable source) {}
并没有太大的不同,只需要在调用网站上进行一点点投射而不是在扩展方法方面(你可能会实现一些取决于特定类型的优化,所以你需要将T转换为IMyInterface或者无论如何) 。 因此,我能想出的唯一理由是,语言设计师希望鼓励您仅以真正通用的方式编写通用扩展。
如果我们对方程中的共同/逆变进行处理,可能会发生一些有趣的事情,这些方程即将在C#4.0中引入。
只是一个想法,但有没有任何理由你不能只从这个类派生并将额外的方法添加到专门化而不是编写一套扩展方法? 我相信你有理由,但只是把它扔出去。
静态类应该能够被定义为抽象。 如果他们能够这样做,那么你可以指定它们不能直接使用,但必须像普通类一样inheritance,然后通用静态类对扩展方法是可行的,因为你可以在抽象类中定义它们,从所述类inheritance,一切都会正常工作。
就像现在一样,静态类有很多重要的限制,这只是在很多情况下真正与mojo混淆的一个。 结合无法使编译器推断返回类型,因此要求您输入整个generics实现来调用通用函数,这些函数没有方法签名中的所有generics,这使得这些东西变得非常脆弱并且使得很多打字不应该在那里。
(还有一种情况是第二个generics在第一个的定义中定义,它不会使用它,即使在编译时也很明显地推断出来。这个非常烦人,因为它非常明显。) MS有很多关于generics的工作,他们可以做些什么来改进这些东西。