为什么不允许在嵌套类中定义扩展方法?

15

我认为在一个嵌套类中编写扩展程序非常方便和合理。主要原因是我可以将该类命名为Extensions,并让它的外部命名空间为编译器提供一个唯一的类型名称。

那么,禁止这样做的技术原因是什么?

public class Foo
{
   ObjectSet<Bar> Bars { get; set; }

   public static class Extensions
   {
      public static Bar ByName(this ObjectSet<Bar> bars, string name)
      {
         return bars.FirstOrDefault(c => c.Name == name);
      }
   }
}

现在我必须创建一个独立的自由类,而不是内部类。

更新/注释:我没有想象内部类会影响扩展方法的可用性范围。我只想解决一个单独命名的实际编码问题。


请勿重复:https://dev59.com/rW865IYBdhLWcg3wNLw7#3934737 - Kevin Aenmey
哎呀,我应该更好地研究重复内容…… - Aaron Anodide
@ReedCopsey,看了你提到的问题的答案,我想我已经提供了一个现实世界的例子,说明为什么它们可能被允许,不是吗? - Aaron Anodide
@ReedCopsey,我缺少一些东西 - 我定义的扩展方法接受 this ObjectSet<Bar> ,只与 Foo 相关,因为 Foo 是其父命名容器(这是我的意图 - 但我开始认为我在另一个层面上不理解某些东西)。 - Aaron Anodide
显示剩余7条评论
3个回答

8

这里的关键点是嵌套类可以访问外部类的私有字段

因此,以下代码有效:

public class Foo
{
    private bool _field;

    public static class Extensions
    {
        public static bool GetField(Foo foo)
        {
            return foo._field;
        }
    }
}

在这里,您明确地传递了一个类的实例,静态方法可以访问私有字段...看起来很合理:

bool fieldValue = Foo.Extensions.GetField(new Foo());

然而,尽管扩展方法只是静态方法的一种替代语法,它们的调用方式与非静态实例方法相同。

如果允许在嵌套类中使用扩展方法,它们实际上可以访问私有字段,并且它们会更接近于实例方法。这可能会导致一些意想不到的后果。

总之,如果允许这样做:

public class Foo
{
    private bool _field;

    public static class Extensions
    {
        public static bool GetField(*this* Foo foo) // not allowed, compile error.
        {
            return foo._field;
        }
    }
}

那么您可以编写以下代码,使扩展方法的行为与实例方法有些类似:

var foo = new Foo();
var iGotAPrivateField = foo.GetField();

评论后的编辑结果

为什么扩展方法等效于实例方法是一个不好的想法?

Eric Lippert的话来说(我来强调):

所以,没错,“扩展方法不是面向对象”的批评完全正确,但也相对不那么重要。扩展方法当然不是面向对象的。它们将操作数据的代码远离声明数据的代码,它们不能破坏封装并访问它们所出现的对象的私有状态,它们与继承不太兼容等等。这是在方便的面向对象形式下进行的过程化编程。


3
在您后面的示例中有什么问题?如果GetField是在Foo类中声明的普通方法,那么它肯定可以访问_field。同样,如果调用Foo.Extensions.GetField(foo),也会期望该方法能够访问_field。那么为什么扩展方法不能呢?确保,如果我设计语言,嵌套类中的扩展方法只能在其直接包围的类中使用,但这种限制将增强扩展方法作为特性的有用性。 - supercat
4
在一个类中,代码可以决定如何将包含在私有字段中的状态公开给外部世界。如果不想公开某些状态,请不要包含公开它的静态方法。请注意,如果上面的扩展方法使用 Bar 作为其 this 参数,则只能访问与 Foo 相关联的变量和类型,而不能访问 Bar 的变量和类型。此外,我希望允许在嵌套类中声明扩展方法的最大原因是允许将它们的范围限制在外部类中(例如...)。 - supercat
1
如果包含扩展方法的类是私有、受保护或内部的,则该方法的使用将被限制在能够看到该类的代码中,并且扩展方法可以接受仅在该外部类中可见的类型参数。对于扩展方法应该是公共的情况,可以通过定义一个公共或内部静态方法并使扩展方法链接到该方法来实现适当的可见性,从而使其内部可见。 - supercat
在需要扩展方法具有广泛可见性并且必须在通用类型上操作的情况下,这种方法通常是必要和适当的(例如,如果使用void DoSomething<T>(this Foo<T> it) where T:class {FooHelper<T>.DoSomething(it);},那么FooHelper<T>可以包含一个ConditionalWeakTable<T,whatever>,但如果该方法仅打算在类Woozle<T>内部使用,则最好在类内部使用非通用调用而不是进行通用类查找。)。 - supercat
2
在我的例子中,使用IEnumerable的扩展方法需要拥有一个实现IEnumerable接口的对象实例,但不一定需要是想要使用扩展方法的类的实例。 - supercat
显示剩余11条评论

2

这并非技术原因,而是实用性考虑。如果您有一个仅限于单个类范围的扩展方法,只需将其定义为类中的常规静态方法并删除“this”。扩展方法用于在多个类之间共享。


3
我猜这背后有技术原因。他不是创建扩展方法来扩展包含类,而是为了扩展与包含类相关的东西。我同意能够这样做会很好。 - Matt Greer
我真的可以用我的示例来做这个吗?扩展方法的目标类型是ObjectSet<Bar> - 我不知道如何向其中添加静态方法。好吧,我重新阅读后现在明白了 - 你会让我定义一个静态方法,该方法将ObjectSet<Bar>作为第一个参数... 公平的,但然后我失去了编写myContext.Bars.ByName(...)这样的句法优雅。我必须编写Foo.BarByName(myContext, ...),那并不可怕,我会同意。 - Aaron Anodide

1

我猜禁止这个东西的想法是因为扩展方法适用于命名空间中的所有实体。

如果您创建了一个嵌套的扩展方法类,则它仅适用于嵌套它的类。在这种情况下,创建扩展方法没有意义。然后任何普通的非扩展方法都可以。


1
任何普通的非扩展方法都可以做到。为什么要阻止控制扩展方法的范围的能力呢? - snarf

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