分部(方法)(C# 参考)
分部成员有一个声明声明,并且通常有一个实现声明。 声明声明不包括正文。 实现声明提供成员正文。 分部成员使类设计器能够提供可以通过源生成器等工具实施的成员挂钩。 分部类型和成员会为人类开发人员提供一种编写类型部分的方法,而工具则编写该类型的其他部分。 如果开发人员不提供可选的实现声明,则编译器可以在编译时删除声明声明。 以下条件适用于分部成员:
- 声明必须以上下文关键字 partial 开头。
- 分部类型各部分中的签名必须匹配。
不允许对构造函数、终结器、重载运算符或事件声明使用 partial
关键字。 在 C# 13 之前,不允许对属性或索引器使用 partial
。
在以下情况下,分部方法不需要可实现声明:
- 没有任何辅助工具修饰符(包括默认的
private
)。 - 它将返回
void
。 - 它没有任何
out
参数。 - 它没有任何以下修饰符:
virtual
、override
、sealed
、new
或extern
。
任何不符合所有这些限制的成员(例如 public virtual partial void
方法)都必须提供实现。 分部属性和索引器必须具有实现。
以下示例演示符合上述限制的分部方法:
partial class MyPartialClass
{
// Declaring definition
partial void OnSomethingHappened(string s);
}
// This part can be in a separate file.
partial class MyPartialClass
{
// Comment out this method and the program
// will still compile.
partial void OnSomethingHappened(string s) =>
Console.WriteLine($"Something happened: {s}");
}
分部成员还可用于与源生成器结合。 例如,可使用以下模式定义 regex:
public partial class RegExSourceGenerator
{
[GeneratedRegex("cat|dog", RegexOptions.IgnoreCase, "en-US")]
private static partial Regex CatOrDogGeneratedRegex();
private static void EvaluateText(string text)
{
if (CatOrDogGeneratedRegex().IsMatch(text))
{
// Take action with matching text
}
}
}
上述示例演示了必须具有实现声明的部分方法。 作为生成的一部分,正则表达式源生成器将会创建实现声明。
以下示例演示了类的声明声明和实现声明。 由于方法的返回类型不是void
(它是 string
),并且其访问权限是 public
,因此,该方法必须具有实现声明:
// Declaring declaration
public partial class PartialExamples
{
/// <summary>
/// Gets or sets the number of elements that the List can contain.
/// </summary>
public partial int Capacity { get; set; }
/// <summary>
/// Gets or sets the element at the specified index.
/// </summary>
/// <param name="index">The index</param>
/// <returns>The string stored at that index</returns>
public partial string this[int index] { get; set; }
public partial string? TryGetAt(int index);
}
public partial class PartialExamples
{
private List<string> _items = [
"one",
"two",
"three",
"four",
"five"
];
// Implementing declaration
/// <summary>
/// Gets or sets the number of elements that the List can contain.
/// </summary>
/// <remarks>
/// If the value is less than the current capacity, the list will shrink to the
/// new value. If the value is negative, the list isn't modified.
/// </remarks>
public partial int Capacity
{
get => _items.Count;
set
{
if ((value != _items.Count) && (value >= 0))
{
_items.Capacity = value;
}
}
}
public partial string this[int index]
{
get => _items[index];
set => _items[index] = value;
}
/// <summary>
/// Gets the element at the specified index.
/// </summary>
/// <param name="index">The index</param>
/// <returns>The string stored at that index, or null if out of bounds</returns>
public partial string? TryGetAt(int index)
{
if (index < _items.Count)
{
return _items[index];
}
return null;
}
}
上述示例演示了关于如何组合这两个声明的规则:
- 签名匹配:一般情况下,声明声明和实现声明的签名必须匹配。 这包括方法、属性、索引器和单个访问器的辅助功能修饰符。 它包括所有参数的参数类型和 ref 类型修饰符。 返回类型和任何 ref 类型修饰符都必须匹配。 元组成员名称必须匹配。 但是,某些规则很灵活:
- 声明声明和实现声明可以具有不同的可为 null 的注释设置。 这意味着,一个可以是 可为 null 的未知声明,另一个是 已启用可为 null 的声明。
- 不涉及未知 Null 性的 Null 性差异会生成警告。
- 默认参数值不需要匹配。 如果方法或索引器的实现声明声明默认参数值,编译器会发出警告。
- 当参数名称不匹配时,编译器会发出警告。 发出的 IL 包含声明声明的参数名称。
- 文档注释:可以包括任一声明中的文档注释。 如果声明声明和实现声明都包括文档注释,则包括实现声明中的注释。 在上述示例中,文档注释包括:
- 对于
Capacity
属性,注释取自实现声明。 当两个声明都有///
注释时,会使用实现声明注释。 - 对于索引器,注释取自声明声明。 实现声明不包括任何
///
注释。 - 对于
TryGetAt
,注释取自实现声明。 声明声明不包括任何///
注释。 - 生成的 XML 具有所有
public
成员的文档注释。
- 对于
- 大多数属性声明会组合在一起。 但是,所有调用方信息属性都使用
AllowMultiple=false
来定义。 编译器可识别声明声明上的任何调用方信息属性。 系统会忽略实现声明上的所有调用方信息属性。 如果在实现声明上添加调用方信息属性,则编译器将发出警告。