使用分部类实现类

已完成

可以将类或方法的定义拆分为两个或多个源文件。 每个源文件都包含类型或方法定义的一部分,所有部分在编译应用程序时合并。

分部类

在以下几种情况下需要拆分类定义:

  • 通过单独的文件声明类可使多个程序员同时处理它。
  • 可以将代码添加到类,而无需重新创建包含自动生成的源的源文件。 Visual Studio 在创建 Windows 窗体、Web 服务包装器代码等时使用此方法。 可以创建使用这些类的代码,而无需修改 Visual Studio 创建的文件。
  • 源生成器可以在类中生成额外的功能。

要拆分类定义,请使用 partial 关键字修饰符。 实际上,每个分部类通常在单独的文件中定义,以便随着时间的推移更轻松地管理和扩展类。

以下 Employee 示例演示了如何将类划分为两个文件:Employee_Part1.csEmployee_Part2.cs


// This is in Employee_Part1.cs
public partial class Employee
{
    public void DoWork()
    {
        Console.WriteLine("Employee is working.");
    }
}


// This is in Employee_Part2.cs
public partial class Employee
{
    public void GoToLunch()
    {
        Console.WriteLine("Employee is at lunch.");
    }
}


//Main program demonstrating the Employee class usage
public class Program
{
    public static void Main()
    {
        Employee emp = new Employee();
        emp.DoWork();
        emp.GoToLunch();
    }
}

// Expected Output:
// Employee is working.
// Employee is at lunch.

partial 关键字指示可以在命名空间中定义类的其他部分。 所有部分都必须使用 partial 关键字。 所有部分必须在编译时可用,才能形成最终类型。 所有部分必须具有相同的可访问性,例如 publicprivate 等。

如果将任何部分声明为抽象,则整个类型被视为抽象。 如果将任何部分声明为密封,则整个类型被视为密封。 如果将任何部分声明为基类型,则整个类型将继承该类。

指定基类的所有部分都必须同意,但省略基类的部分仍继承基类型。 部分可以指定不同的基接口,最终类型实现所有分部声明列出的所有接口。 在分部定义中声明的任何类、结构或接口成员都可用于所有其他部分。 最终类型是编译时所有部分的组合。

注意

委托或枚举声明上不提供 partial 修饰符。

下面的示例演示了嵌套的类型可以是分部类型,即使嵌套在其中的类型本身不是分部类型。


class Container
{
    partial class Nested
    {
        void Test() { }
    }

    partial class Nested
    {
        void Test2() { }
    }
}

在编译时,将合并分部类型定义的特性。 例如,请考虑以下声明:


[SerializableAttribute]
partial class Moon { }

[ObsoleteAttribute]
partial class Moon { }

它们等效于以下声明:


[SerializableAttribute]
[ObsoleteAttribute]
class Moon { }

以下是从所有分部类型定义合并的:

  • XML 注释。 但是,如果部分成员的两个声明都包含注释,则仅包括实现成员的注释。
  • 接口
  • 泛型类型参数特性
  • 类特性
  • 成员

例如,请考虑以下声明:


partial class Earth : Planet, IRotate { }
partial class Earth : IRevolve { }

它们等效于以下声明:


class Earth : Planet, IRotate, IRevolve { }

对分部类定义的限制

使用分部类定义时,需要遵循以下几个规则:

  • 所有分部类型定义要求同一类型的部分必须使用 partial 修改。 例如,以下类声明生成错误:

    
    public partial class A { }
    //public class A { }  // Error, must also be marked partial
    
    
  • partial 修饰符只能出现在关键字 class、struct 或 interface 之前。

  • 在分部类型定义中允许嵌套分部类型,如以下示例所示:

    
    partial class ClassWithNestedClass
    {
        partial class NestedClass { }
    }
    
    partial class ClassWithNestedClass
    {
        partial class NestedClass { }
    }
    
    
  • 所有分部类型定义要求同一类型的部分都必须在同一程序集和同一模块(.exe 或 .dll 文件)中定义。 分部定义不能跨越多个模块。

  • 类名和泛型类型参数必须匹配所有分部类型定义。 泛型类型可以是分部类型。 每个分部声明必须按相同顺序使用相同的参数名称。

  • 分部类型定义的以下关键字是可选的,但如果在一个分部类型定义上存在,则必须在同一类型的其他分部定义上指定相同的关键字:

    • public
    • 专用
    • protected
    • 内部
    • abstract
    • 密封
    • 基类
    • new 修饰符(嵌套部分)
    • 泛型约束

实现分部类

在下面的示例中,Coords 类的字段和构造函数在一个分部类定义 (Coords_Part1.cs) 中声明,PrintCoords 方法在另一个分部类定义 (Coords_Part2.cs) 中声明。 这种分离演示了如何跨多个文件划分分部类,以便更轻松地保持可维护性。


// This is in Coords_Part1.cs
 public partial class Coords
 {
     private int x;
     private int y;

     public Coords(int x, int y)
     {
         this.x = x;
         this.y = y;
     }
 }

 // This is in Coords_Part2.cs
 public partial class Coords
 {
     public void PrintCoords()
     {
         Console.WriteLine("Coords: {0},{1}", x, y);
     }
 }

// Main program demonstrating the Coords class usage
 class TestCoords
 {
     static void Main()
     {
         Coords myCoords = new Coords(10, 15);
         myCoords.PrintCoords();

         // Keep the console window open in debug mode.
         Console.WriteLine("Press any key to exit.");
         Console.ReadKey();
     }
 }
 // Output: Coords: 10,15

分部成员

分部类可以包含分部成员。 类的一部分包含成员的签名。 可以在同一部分或另一部分中定义实现。

当签名遵循以下规则时,分部方法不需要实现:

  • 声明不包含任何访问修饰符。 默认情况下,该方法具有专用访问。

  • 返回类型为 void

  • 没有任何参数具有 out 修饰符。

  • 方法声明不能包括以下任何修饰符:

    • virtual
    • 替代
    • 密封
    • extern

当没有实现时,该方法和对方法的所有调用都将在编译时移除。

任何不符合所有这些限制的方法(包括属性和索引器)都必须提供实现。 该实现可能由源生成器提供。 不能使用自动实现的属性实现分部属性。 编译程序无法区分自动实现的属性和分部属性的声明。

从 C# 13 开始,分部属性的实现声明可以使用字段支持的属性来定义实现声明。 字段支持的属性提供简洁的语法,其中 field 关键字访问该属性的编译程序合成支持字段。 例如,可以编写以下代码:


// in file1.cs
public partial class PropertyBag
{
    // Defining declaration
    public partial int MyProperty { get; set; }
}

// In file2.cs
public partial class PropertyBag
{
    // Defining declaration
    public partial int MyProperty { get => field; set; }
}

可以在 fieldget 访问器中使用 set,也可以同时使用两者。

重要说明

field 关键字是 C# 13 中的预览功能。 必须使用 .NET 9,并将 <LangVersion> 元素设置为在项目文件中预览,才能使用 field 上下文关键字。

应小心在具有名为 field 的字段的类中使用 field 关键字功能。 新的 field 关键字在属性访问器的范围内隐藏一个名为 field 的字段。 可以更改字段变量的名称,也可以使用 @ 令牌将字段标识符引用为 @field

分部方法使类的一个部分的实现者能够声明成员。 类的另一部分的实现者可以定义该成员。 这种分离在两种情况下很有用:生成样板代码的模板和源生成器。

  • 模板代码:模板保留方法名称和签名,以便生成的代码可以调用该方法。 这些方法遵循限制,使开发人员能够决定是否实现该方法。 如果未实现该方法,则编译程序将移除方法签名和对该方法的所有调用。 对该方法的调用(包括调用中对自变量的评估所产生的任何结果)在运行时不起作用。 因此,即使未提供实现,分部类中的任何代码都可以自由使用分部方法。 如果调用方法但不实现,则不会生成编译时或运行时错误。

  • 源生成器:源生成器为成员提供实现。 人类开发人员可以添加成员声明(通常是由源生成器读取的特性)。 开发人员可以编写调用这些成员的代码。 源生成器在编译期间运行,并提供实现。 在这种情况下,不遵循通常可能未实现的分部成员的限制。

    
    // Definition in file1.cs
    partial void OnNameChanged();
    
    // Implementation in file2.cs
    partial void OnNameChanged()
    {
      // method body
    }
    
    
  • 分部成员声明必须以上下文关键字 partial 开头。

  • 分部类型的两个部分中的分部成员签名必须匹配。

  • 分部成员可以具有 static 和 unsafe 修饰符。

  • 分部成员可以是泛型成员。 约束在定义和实现方法声明上必须相同。 参数和类型参数名称在实现声明中不必与定义参数名称相同。

  • 可以对定义和实现的分部方法进行委托,但不能对没有实现的分部方法进行委托。