如果实现在同一个程序集中,为什么部分方法不能公开?

Sim*_*mon 17 .net c# partial-classes

根据MSDN文档的部分类:

部分方法是隐式私有的

所以你可以拥有这个

// Definition in file1.cs
partial void Method1();

// Implementation in file2.cs
partial void Method1()
{
  // method body
}
Run Code Online (Sandbox Code Playgroud)

但你不能拥有这个

// Definition in file1.cs
public partial void Method1();

// Implementation in file2.cs
public partial void Method1()
{
  // method body
}
Run Code Online (Sandbox Code Playgroud)

但为什么会这样呢?是否有某些原因编译器无法处理公共部分方法?

Ree*_*sey 27

部分方法必须在编译时完全可解析.如果它们在编译时不存在,则它们在输出中完全丢失.部分方法工作的全部原因是删除它们对一行调用站点之外的API或程序流没有影响(这也是他们必须返回void的原因).

向公共API添加方法时 - 您正在为其他对象定义合同.由于部分方法的全部要点是使其成为可选的,你基本上会说:"我有一份你可以信赖的合同.哦等等,你不能依靠这种方法."

为了使公共API合理,部分方法必须始终存在,或者总是消失 - 在这种情况下它不应该是部分的.

从理论上讲,语言设计者可以改变部分方法的工作方式,以便实现这一点.他们可以使用存根(即:不执行任何操作的方法)实现它们,而不是从被调用的任何地方删除它们.这不会那么有效,并且对于部分方法所设想的用例是不必要的.

  • 正确.这样做会完全违背部分方法的目的,即它们在元数据负担中"付出代价".您可以拥有一千个部分方法,如果只定义其中一个方法,则会获得为该一个方法发出的元数据,而不是全部方法.我们为机器生成的编码场景设计了部分方法,其中可能有数千个,并且我们不希望膨胀元数据. (6认同)

Aar*_*ght 8

不要问为什么他们是私人的,让我们重新解释这个问题:

  • 如果部分方法不是私有的,会发生什么?

考虑这个简单的例子:

public partial class Calculator
{
    public int Divide(int dividend, int divisor)
    {
        try
        {
            return dividend / divisor;
        }
        catch (DivideByZeroException ex)
        {
            HandleException(ex);
            return 0;
        }
    }

    partial void HandleException(ArithmeticException ex);
}
Run Code Online (Sandbox Code Playgroud)

让我们暂时忽略为什么我们将这个作为局部方法而不是抽象方法的问题(我将回过头来看).重要的是,无论HandleException方法是否实现,这都会编译 - 并且正常工作.如果没有人实现它,这只是吃异常并返回0.

现在让我们改变规则,说部分方法可以得到保护:

public partial class Calculator
{
    // Snip other methods

    // Invalid code
    partial protected virtual void HandleException(ArithmeticException ex);
}

public class LoggingCalculator : Calculator
{
    protected override virtual void HandleException(ArithmeticException ex)
    {
        LogException(ex);
        base.HandleException(ex);
    }

    private void LogException(ArithmeticException ex) { ... }
}
Run Code Online (Sandbox Code Playgroud)

我们这里有点问题.我们已经"覆盖"了这个HandleException方法,除了没有方法可以覆盖它.我的意思是该方法字面上不存在,它根本没有编译.

我们的基地Calculator调用HandleException什么意思?它应该调用派生(重写)方法吗?如果是这样,编译器为基本HandleException方法发出了什么代码?它应该变成抽象方法吗?一个空方法?当派生方法调用时会发生什么base.HandleException?这应该什么都不做?举起一个MethodNotFoundException?这里真的很难遵循最不惊讶的原则; 几乎你做的任何事情都会令人惊讶.

或者也许在HandleException调用时不会发生任何事情,因为没有实现基本方法.但这似乎并不直观.我们派生的类已经去了并实现了这个方法,并且基类已经消失,并且在我们不知道的情况下从它下面拉出了地毯.我可以很容易地想象一些可怜的开发人员将他的头发拉出来,无法弄清楚为什么他的被覆盖的方法永远不会被执行.

或者这个代码可能根本不应该编译或者应该产生警告.但这有很多问题.最重要的是,它打破了部分方法提供的契约,即忽略实现一个方法永远不会导致编译器错误.你有一个很好的基类,然后,由于有人在应用程序的一些完全不同的部分实现了一个完全有效的派生类,突然你的应用程序被打破了.

我甚至还没有开始讨论基类和派生类在不同程序集中的可能性.如果引用具有包含"公共"部分方法的基类的程序集,并且尝试在另一个程序集中的派生类中覆盖它,会发生什么?那里的基本方法是否存在?如果最初实现该方法,并且我们针对它编写了一堆代码,但有人决定删除实现,该怎么办?编译器无法从引用类中删除部分方法调用,因为就编译器而言,该方法从未存在过.它不存在,它不再在编译程序集的IL中.所以现在,简单地通过删除部分方法的实现,该方法应该没有任何不良影响,我们已经破坏了一大堆依赖代码.

现在有些人可能会说,"那么,我知道我不会尝试用部分方法来做这种非法的事情." 你必须要理解的是,部分方法 - 很像部分类 - 主要是为了帮助简化代码生成的任务.这是非常不可能的,你任何时候都希望自己写的部分方法周期.随着机器生成的代码,而另一方面,它实际上很可能是代码的消费者会希望在不同的位置,以"注入"的代码,以及部分方法提供了这样做的清洁方式.

其中存在问题.如果由于部分方法而引入编译时错误的可能性,则会创建一种代码生成器生成无法编译的代码的情况.这是一个非常非常糟糕的情况.想想如果你最喜欢的设计工具 - 比如Linq to SQL,或Winforms或ASP.NET设计师,突然开始生成有时无法编译的代码,你会做什么因为其他一些程序员创建了一些你以前从未见过的其他类,这种类似乎与部分方法有点过于亲密?

最后,它实际上归结为一个更简单的问题:公共/受保护的部分方法会添加什么,你无法用抽象方法完成?partials背后的想法是你可以把它们放在具体的类上,它们仍然可以编译.或者更确切地说,它们不会编译,但它们也不会产生错误,它们将被完全忽略.但是如果你希望它们被公开调用,那么它们就不再是"可选的"了,如果你希望它们在派生类中被覆盖,那么你也可以将它抽象为虚拟或虚空.对于公共或受保护的部分方法没有任何用处,除了混淆编译器和可怜的混蛋试图弄清楚这一切.

因此,团队没有打开Pandora的盒子,而是忘记了 - 公共/受保护的部分方法无论如何都没有用,所以只需将它们设为私有.这样我们就可以保证一切安全和理智.它是.只要部分方法保持私密,它们就很容易理解和无忧.让我们保持这种方式!

  • @Simon:如果"你的例子"没有使用特定的结构,为什么会这么重要?我正在解释为什么该功能无法安全实施.您不是C#编译器的唯一用户.此外,如上所述,部分方法被设计用于代码生成; 你的用例可能甚至不在雷达上. (3认同)

Joh*_*ons 6

因为MS编译器团队没有要求实现此功能.

这是MS内部发生的可能情况,因为VS使用代码gen来实现其许多功能,有一天MS代码开发人员决定他/她需要部分方法,以便代码生成的api可以通过外人,这个要求促使编译团队采取行动和交付.