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

eiee3dmh  于 2023-04-22  发布在  .NET
关注(0)|答案(9)|浏览(130)

根据MSDN Documentation对于部分类:
分部方法是隐式私有的
这样你就可以拥有这个了

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

// Implementation in file2.cs
partial void Method1()
{
  // method body
}

但你不能拥有这个

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

// Implementation in file2.cs
public partial void Method1()
{
  // method body
}

但是为什么会这样呢?是不是有什么原因导致编译器不能处理公共分部方法?

pxq42qpu

pxq42qpu1#

Partial方法在编译时必须是完全可解析的。如果它们在编译时不存在,它们将完全从输出中丢失。partial方法工作的全部原因是删除它们对API或程序流没有影响(这也是为什么它们必须返回void)。
当你在公共API中添加一个方法时,你是在为其他对象定义一个合约。因为partial方法的全部意义就是让它成为可选的,所以你基本上是在说:“我有一份契约,你可以依靠,哦,不对,你不能依靠这个方法。”
为了使公共API合理,partial方法必须始终存在,或者始终消失-在这种情况下,它不应该是partial的。
从理论上讲,语言设计者可以改变分部方法的工作方式,以允许这一点。他们可以使用存根实现它们,而不是从调用它们的任何地方删除它们。但是,这将不是高效的,并且对于部分方法所设想的用例是不必要的。

2izufjch

2izufjch2#

与其问 * 为什么它们是私有的 *,不如换个说法:

*如果partial方法 * 不是 * private会发生什么?

考虑这个简单的例子:

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);
}

让我们暂时忽略为什么我们要把它作为一个部分方法而不是一个抽象方法的问题(我会回到这个问题)。重要的是,无论HandleException方法是否被实现,它都能编译并正确工作。如果没有人实现它,它就会吃掉异常并返回0。
现在让我们改变规则,假设partial方法可以被保护:

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) { ... }
}

这里有一个小问题。我们已经“覆盖”了HandleException方法,只是还没有方法可以覆盖。我的意思是这个方法实际上 * 不存在 *,它根本没有被编译。
我们的基Calculator调用HandleException意味着什么?(重写)方法?如果是,编译器为基本HandleException方法发出什么代码?它应该被转换为抽象方法吗?空方法?当派生方法调用base.HandleException时会发生什么?这应该什么都不做吗?引发一个MethodNotFoundException?它It’在这里很难遵循最小惊奇原则;你做的任何事都会令人惊讶
或者当HandleException被调用的时候什么都不会发生,因为基本方法没有被实现。这看起来不是很直观。我们的派生类已经实现了这个方法,而基类已经在我们不知道的情况下把地毯从下面拉出来了。我可以很容易地想象一些可怜的开发人员把头发拉出来,无法弄清楚为什么他的重写方法永远不会被执行。
或者这段代码根本就不应该编译,或者应该产生一个警告。但是这本身有很多问题。最重要的是,它破坏了分部方法提供的契约,即忽略实现一个分部方法永远不会导致编译器错误。你有一个基类,它运行得很好,然后,由于某人在应用程序的某个完全不同的部分实现了一个完全有效的派生类 *,突然间你的应用程序就坏了。
我甚至还没有开始讨论基类和派生类在不同程序集中的可能性。如果你用一个包含“公共”分部方法的基类引用一个程序集,然后你试图在另一个程序集中的派生类中重写它,会发生什么?基类在那里,还是不在那里?如果,最初,方法被实现了,我们写了一堆代码来反对它,那会发生什么?但是有人决定删除实现?编译器没有办法从引用类中删除部分方法调用,因为就编译器而言,该方法 * 从一开始就不存在 *。它不在那里,它不再在编译后的程序集的IL中。所以现在,简单地通过删除部分方法的实现,这应该没有不良影响,我们已经破解了一大堆依赖代码。
现在有些人可能会说,“那又怎样,我知道我不会尝试用分部方法来做这种非法的事情。”你必须明白的是分部方法--很像分部类--主要是为了帮助简化代码生成的任务。你不太可能想自己写一个分部方法 * 句号 *。用 * 机器生成的 *另一方面,代码的使用者实际上很可能希望在不同的位置“注入”代码,而分部方法提供了一种干净的方法来完成这一任务。
问题就在这里。如果你引入了由于partial方法导致的编译时错误的可能性,你就创造了一种情况,即代码生成器生成无法编译的代码。这是一种非常非常糟糕的情况。想想如果你最喜欢的设计器工具-比如Linq to SQL,或者Winforms或ASP.NET设计器,突然开始产生有时无法编译的代码,这一切都是因为其他程序员创建了一些你以前从未见过的其他类,而这些类恰好与分部方法变得有点太亲密了?
最后,它实际上归结为一个简单得多的问题:public/protected partial方法能增加什么是抽象方法无法完成的?partial背后的想法是,你可以把它们放在具体的类上,它们仍然可以编译。或者更确切地说,它们不会编译,但也不会产生错误,它们将被完全忽略。但是如果你希望它们被公开调用,那么它们就不再是真正的“可选”了。如果你希望它们在派生类中被重写,那么你最好把它变成抽象的或虚的和空的。对于一个公共的或受保护的分部方法来说,除了混淆编译器和试图理解它的可怜虫之外,没有任何真正的用途。

因此,团队没有打开潘多拉的盒子,而是说算了吧- public/protected partial方法反正没有多大用处,所以就让它们私有化吧。这样我们就可以保持一切安全和理智。事实也确实如此。只要partial方法保持私有,它们就很容易理解,也不用担心。让我们保持这种状态吧!

syqv5f0l

syqv5f0l3#

因为MS编译器团队没有实现此功能的需求。
这是MS内部可能发生的情况,因为VS使用代码生成器来实现其许多功能,有一天MS代码生成器开发人员决定他/她需要有部分方法,以便代码生成的API可以由外部人员扩展,这一要求导致编译器团队采取行动并交付。

xe55xuns

xe55xuns4#

如果你没有定义方法,编译器应该怎么做?
如果你不定义它们,分部方法根本不会被编译。
这是唯一可能的,因为编译器知道所有对方法的调用在哪里。如果方法没有定义,它将完全删除调用方法的代码行。(这也是为什么它们只能返回void
如果该方法是公共的,则这将无法工作,因为该方法可能被另一个程序集调用,而编译器无法控制该程序集。

xe55xuns

xe55xuns5#

里德和斯拉克的答案是完全正确的,但你似乎不愿意接受他们的答案。
因此,我将尝试解释 * 为什么 * 部分方法是用这些限制实现的。
分部方法用于以最高效率实现某些类型的代码生成方案,包括执行时间和 meta数据开销。最后一部分是他们的真实的原因,因为他们试图让他们(用Erics的话说)“为玩而付费”。
当添加了partial方法时,JIT完全能够内联一个空方法,因此它在调用站点上的运行时工作量为零。问题是,即使这样,也会有一个成本,即类的 meta数据将使这些空方法增加它们的大小(不必要地),并在JIT过程中强制更多的工作来处理优化它们。
虽然你可能不太担心这个成本(实际上很多人根本不会注意到它),但它确实对启动成本重要的代码有很大的影响,或者磁盘/内存受到限制。你可能已经注意到现在在windows移动的7和Zune上强制使用.Net,在这些领域,类型元数据的膨胀是一个相当大的成本。
因此,部分方法被设计为,如果它们从未被使用,它们的成本绝对为零,它们将以任何方式不再存在于输出中。这带来了一些重要的约束,以确保这不会导致错误。
msdn page与我的笔记。

  • ...该方法必须返回void。
  • 分部方法可以有ref但不能有out参数。

否则,删除对它们的调用可能会给您留下一个未定义的问题,即用什么来替换赋值。

  • 分部方法不能是外部的,因为主体的存在决定了它们是定义还是实现。
  • 可以对已定义和实现的分部方法进行委托,但不能对仅定义的分部方法进行委托。

这是因为编译器需要知道它是否被定义了,因此删除它是安全的。

  • 分部方法是隐式私有的,因此它们不能是虚的。

这个特性的metal模型是,如果编译器知道一个partial方法被实现了,那么它应该简单地允许这个partial方法是公共的(并且是虚拟的),因为它可以检查你是否实现了这个方法。
如果要更改此功能,您有两个选择:

  • 强制所有非私有的分部方法 require 实现。
  • 简单并且不太可能涉及太多的努力,但是然后任何这样的方法在原始计划的有意义的意义上不再是局部的。
  • 任何声明为公共的方法如果未在程序集中实现,则会直接删除。
  • 这允许应用部分移除
  • 这需要编译器付出更多的努力(检测对方法的所有引用,而不是简单地查看组合类本身)
  • IntelliSense实现有点混乱,是否应该只在给出定义时才显示方法?
  • 重载解决方案变得更加复杂,因为您需要决定对这种没有定义的方法的调用是a)编译时失败还是b)导致选择下一个最佳选项(如果可用)。
  • 在private only的情况下,在调用点的表达式中的副作用已经很复杂了。通过假设部分实现已经表现出高度的耦合,这在一定程度上减轻了副作用。这种变化将增加耦合的可能性。
  • 这有相当复杂的失败模式,因为公共方法可能会被原始程序集中的一些无害更改悄悄删除。依赖于此程序集的其他程序集将失败(在编译时),但会出现非常混乱的错误(不需要相当大的努力,这甚至会应用于同一解决方案中的项目)。

解决方案1是毫无意义的,它增加了工作量,对最初的目标没有任何好处。更糟糕的是,它实际上可能会阻碍最初的目标,因为使用部分方法的人可能没有意识到他们没有减少元数据。此外,有人可能会对由于未能提供定义而导致的错误感到困惑。
这就剩下解决方案2了。这个解决方案涉及到努力(和every feature starts with -100),所以它必须添加一些引人注目的好处来让它超过-100(以及额外的负面影响,因为不是额外的边缘情况造成的混乱)。你能想出什么场景来让事情变得积极?
你在上面的注解中的激励性例子是“在不同的文件中有注解和/或属性”
XML注解的动机完全是为了增加文档和代码的本地性。如果它们的冗长程度很高,那么大纲的存在就是为了减轻这一点。我的观点是,这不足以使该功能值得使用。

在我看来,将属性推送到单独位置的能力并没有那么有用,事实上,我认为必须在两个位置进行查看更令人困惑。当前的private only实现也有这个问题,但这是不可避免的,并且通过假设在类外部不可见的高耦合并不像在类外部的高耦合那么糟糕而有所缓解。
如果你能证明一些其他令人信服的理由,我相信这将是有趣的,但要克服的负面影响是相当大的。

raogr8fs

raogr8fs6#

我已经阅读了你所有的评论,我同意大多数的结论,但是我有一个场景,我认为它会从公共/受保护的部分方法中受益,而不会失去最初的意图:
我有一个代码生成器,它可以生成序列化代码和其他样板代码。特别是,它为每个属性生成一个“Reset”方法,以便像VS这样的设计器可以将其值恢复为原始值。在此方法的代码中,我生成了对部分方法Repaint()的调用。
这个想法是,如果对象想要,它可以为它编写代码,然后做一些事情,否则什么也不做,性能是最佳的。
问题是,有时,Repaint方法存在于对象中的目的不是从生成的代码中调用,在这一点上,当我声明方法主体时,我应该能够使其成为内部的,受保护的或公共的。我在这一点上定义了方法,是的,我会记录下来,等等。这里,不是在我生成的声明中,而是在我手工创建的声明中。它也在生成的代码中定义的事实不应该影响这一点。
我同意在partial方法中定义访问修饰符是没有意义的,当你声明一个没有body的方法时。当你声明一个有body的方法时,你应该可以自由地以任何你想要的方式声明它。我看不出编译器接受这种情况在复杂性方面有什么区别。
注意,在分部类中,这是非常好的,我可以让一个分部声明“裸”而不带任何访问修饰符,但我可以在同一个类的另一个分部声明中指定它们。只要没有矛盾,一切都很好。

e37o9pze

e37o9pze7#

如果partial方法没有实现,它将被编译器删除。根据我的理解,如果partial方法可以作为public访问,编译器将无法删除partial方法

//partial class with public modifier
public partial class Sample 
{
    partial void Display();
}

public partial class Sample
{
    partial void Display() { }

}

我们将能够像下面这样访问public方法,这将限制编译器删除未实现的partial方法。

// referred partial method which doesn't have implementation 
var sample = new Sample();
sample.Display();
z4iuyo4d

z4iuyo4d8#

原因很简单,因为partial方法是一个实现细节,所以它们不是公共的。它们主要是为了支持设计器相关的场景,而不是为了成为受支持的公共API的一部分。非公共方法在这里工作得很好。
允许部分方法是公共的是一个特性。特性有固有的成本,包括设计,测试,开发等...部分方法只是在非常拥挤的Visual Studio 2008中添加的许多特性之一。它们的范围尽可能小,以适应场景,以便为更紧迫的特性(如LINQ)留出空间。

yh2wf1be

yh2wf1be9#

不确定这是否能回答你的问题,它确实回答了我的问题。Partial Methods
摘录:
如果允许您返回一些东西,并且反过来使用它作为CallMyMethods函数的返回值,那么当部分方法没有实现时,您就会遇到麻烦。

相关问题