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