分割/合并部分类方法。

20

我知道部分方法可以用于将方法的定义拆分到多个文件中。但是我想知道,如果每个文件中都包含代码,这样做是否允许?

例如,假设我有一个方法 private partial void Foo()。我在文件A和文件B中都定义了它。两个实例中是否都可以包含该方法的代码,还是只能选其中一个?如果允许两个都包含代码,那我会感到惊讶。


4
要找出答案,就直接尝试一下。但是说真的,如果允许这样做,你希望会发生什么? - AnthonyWJones
1
你是否在考虑部分类,它们可以分为多个源单元?(例如,请参见WinForms或WPF窗口。) - JMD
3
请参考我的这篇文章,了解更多关于这个主题的细节。http://blogs.msdn.com/ericlippert/archive/2009/09/14/what-s-the-difference-between-a-partial-method-and-a-partial-class.aspx - Eric Lippert
7个回答

22

不行,你不能这样做。如果可以的话,当你调用Foo()时,哪个代码会先执行呢?如果两个版本都在处理(并修改)全局状态,那么知道执行顺序就非常重要。

无论如何,这是没有意义的。所以不行,你不能这样做

##恶劣示例1 作为这种可能性出现的潜在恶劣行为的一个简单示例,假设你可以这样做,并且假设你有以下代码:

public partial class MyClass {
    private int count = 0;
    public partial void NastyMethod() {
        count++;
    }
}

public partial class MyClass {
    public partial void NastyMethod() {
        Console.WriteLine(count);
    }
}

当你调用 NastyMethod(),它会打印什么值?没有任何意义!
## 糟糕的例子 2
现在又出现了一个奇怪的问题。该怎么处理参数和返回值呢?
public partial class MyClass2 {
    public partial bool HasRealSolution(double a, double b, double c) {
        var delta = b*b - 4*a*c;
        return delta >= 0;
    }
}

public partial class MyClass2 {
    public partial bool HasRealSolution(double a, double b, double c) {
        return false;
    }
}

现在,我们该如何理解这段代码呢?在调用HasRealSolution(1, 2, 1)后,应该考虑哪个返回值呢?一个方法怎么可能有两个不同的、同时存在的返回值*?我们可不是在处理非确定有限状态自动机

对于那些认为在这个假设的世界中我的不存在的部分方法应该是void的人,请将return替换为将某个私有字段设置为该类的值。效果几乎相同。

*请注意,我所说的不是由两个值组成的单个返回值,比如Tuple<T1, T2>。我说的是两个返回值。(???)


9
该语言在理论上可能有一种结构来处理那种情况(但它没有)。 - jason
2
+1给@Jason的评论 -- @Bruno: 不过这并不是一个有说服力的理由。它可以按顺序执行两者而不保证执行顺序(可能会有点混乱,但绝对是可行的)。 - Mehrdad Afshari
2
@Mehrdad,这样的架构非常脆弱。无论如何,它没有任何目的。 - Bruno Reis
4
@Bruno:对于在分部类的不同部分定义的初始化表达式,已经有类似这样的情况了。执行顺序不能保证。我同意你的看法,即不允许在C#中这样做是一个好主意,但这并不是不允许的“原因”。这不可能是由于技术限制而不可能实现,而是因为他们选择使其不可能。 - Mehrdad Afshari
5
partial方法必须是void - Mehrdad Afshari
显示剩余6条评论

22

这实际上是对@Bruno答案的评论,可能与问题不完全相关:

在语言设计中,不允许部分类具有多个实现的决定是一个任意的决定。您提出的观点是您可能会决定不允许这种事情发生在您的语言中的一个很好的理由,但实际上并没有技术限制。您可以很容易地决定允许多个实现,并让编译器决定实现的执行顺序。实际上,C#规范已经有了关于“partial”类顺序未定义的情况:

// A.cs:
partial class Program {
    static int x = y + 42;
    static void Main() {
       System.Console.WriteLine("x: {0}; y: {1}", x, y);
    }
}

// B.cs:
partial class Program {
    static int y = x + 42;
}

这是符合 C# 3.0 规范的有效代码,但输出可能会是:

x: 42; y: 84
或者
x: 84; y: 42

编译器可以生成其中任意一个,甚至不会发出警告。

C#语言要求partial方法返回void类型。partial方法的签名最多只能定义一次。同样地,实现也应该最多定义一次。


1
这太棒了!非常奇怪。你不觉得编译器应该检测到它,并且至少生成一个警告吗?甚至可能是一个错误! - Bruno Reis
2
@Bruno:我认为partial应该保留用于代码生成。如果确实需要将单个类中的内容分离到多个文件中,那么这个类可能太大了,应该拆分成多个类。 - Mehrdad Afshari

9

4
实际上,声明多个partial方法实例也是错误的。最多只能存在一个声明和一个实现。 - Mehrdad Afshari

5
不,这是不可能的。另外,你有一点误解。部分类方法的目的不是“将方法定义分割成多个文件”。
相反,部分类方法旨在将类的定义分割成多个文件。这很有帮助,特别是对于自动生成的代码,因为它允许你定义“可选”的方法,你可以自由实现,但也可以自由忽略。

4
正如某个回复者所指出的那样,没有技术上的理由可以排除在部分类中的多个部分中使用多个部分方法实现。现在流行的是,根据Microsoft DOCs创建可以在Silverlight中运行并且也可以在完整CLR下运行的程序集,具体取决于包含哪些文件。微软自己的示例在Silverlight兼容文件中包括了部分类方法定义,并将它们的实现包含在其他应该在CLR构建中包含的文件中。
当然,如果允许多个实现,开发人员必须意识到调用顺序是不确定的。一个例子是一个方法,它接受一个指向bool类型的单一引用,该引用指示当前环境能够执行“某些操作”。
例如:
/// <summary>
/// Method will determine whether a type is serializable.
/// </summary>
/// <param name="type">
/// The Type to be checked for serializability.
/// </param>
/// <param name="serializable">
/// ref variable to be set to <c>true</c> if serializable. If
/// <paramref name="serializable"/> is already <c>true</c>, the
/// method should simply return.
/// </param>

静态部分方法 IsTheTypeSerializable(Type type, ref Boolean serializable);

该方法可用于实现包含多个选项的类型序列化框架,具体取决于混合了哪些部分类以及包含了哪些序列化程序。即使只有单个实现(根据编译器规则所能存在的全部),实现也必须按照上述方法文档中描述的逻辑进行,因为无法保证在定义部分中的哪个时间点调用该方法。如果允许多个实现,则整体逻辑必须保证结果不会依赖于实现调用的顺序。

在我们的框架中,我们实际上使用可加载的委托来执行不同情形下的可序列化性分析。然而,对于这种情况确实允许多个实现将是方便的。

我们推测 Microsoft 害怕新手开发人员使用这个危险特性会导致混乱。使用此功能需要知道自己在做什么。


3

这是不可能的。你在一个地方定义签名,在另一个地方实现。

详情请参见MSDN


它们不需要在单独的文件中定义。它们甚至不需要在类的不同“部分”中定义。 - Mehrdad Afshari
@Mehrdad Afshari:你说得对,感谢澄清。 - jason

2

你不能合并这些方法,但是你可以使用部分类方法来设置一个链。如果你想避免代码编织,这是一个选择。我不是说这是一个很好的选择,但它是一个选择。

public partial class MyClass {
    private int count = 0;
    public partial void NastyMethod() {
        count++;
    }
}

public partial class MyClass {
    public partial void NastyMethod() {
        Console.WriteLine(count);
    }
}
   

更改至。

public partial class MyClass {
    private int count = 0;
    public partial void NastyMethod() {
        count++;
        OnNastyMethodExecuted(count);
    }

partial void OnNastyMethodExecuted(int Value);
}

public partial class MyClass {
    partial void OnNastyMethodExecuted(int value) {
        Console.WriteLine(value);
    }
}

C# 9之前和代码生成器添加之前的规则:

  • 部分类中的分部方法由partial修饰符表示。
  • 分部方法必须是private。
  • 分部方法必须返回void。
  • 分部方法只能在分部类中声明。
  • 分部方法不一定总有实现。
  • 分部方法可以是静态和泛型的。
  • 分部方法可以具有包括ref但不包括out在内的参数。
  • 不能将委托设置为分部方法。

C# 9之后的规则:

  • 分部方法可以有返回类型。

  • 分部方法可以有out参数。

  • 分部方法可以具有访问修饰符。

  • 然而,如果有访问修饰符,则必须有实现。

https://www.codeproject.com/Articles/30101/Introduction-to-Partial-Methods


值得注意的是,从C# 9开始,这些规则基本上被颠倒了... - ScottishTapWater

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