Skip to content

类与接口

类和接口是面向对象编程(OOP)的两个核心概念。它们提供了不同的抽象方式来定义对象的行为和结构。

访问修饰符

在 C# 编程中,访问修饰符用于控制类、结构、接口、方法和属性等成员的可见性和访问级别。正确使用访问修饰符可以增强代码的封装性、安全性和可维护性。

访问修饰符类内部同一类的派生类同一命名空间同一程序集不同命名空间和程序集
public
protected internal
internal
protected
private protected是(同一程序集)
private

类是C#中用于定义对象的蓝图或模板。它可以包含数据和行为。类是C#中实现对象的基本单元。

特点:

  1. 封装(Encapsulation):类将数据和方法封装在一起,通过属性和方法对外公开。
  2. 继承(Inheritance):一个类可以继承另一个类,从而重用代码。C#只支持单继承,但可以通过接口实现多重继承的效果。
  3. 多态(Polymorphism):类可以通过虚方法和重写实现多态,允许子类修改父类的行为。
  4. 抽象(Abstraction):类可以通过抽象类和方法来实现部分抽象。
成员描述
字段字段是在类范围声明的变量。 字段可以是内置数值类型或其他类的实例。 例如,日历类可能具有一个包含当前日期的字段。
常量常量是在编译时设置其值并且不能更改其值的字段。
属性属性是类中可以像类中的字段一样访问的方法。 属性可以为类字段提供保护,以避免字段在对象不知道的情况下被更改。
方法方法定义类可以执行的操作。 方法可接受提供输入数据的参数,并可通过参数返回输出数据。 方法还可以不使用参数而直接返回值。
事件事件向其他对象提供有关发生的事情(如单击按钮或成功完成某个方法)的通知。 事件是使用委托定义和触发的。
运算符重载运算符被视为类型成员。 重载运算符时,将其定义为类型中的公共静态方法。 有关详细信息,请参阅运算符重载。
索引器使用索引器可以用类似于数组的方式为对象建立索引。
构造函数构造函数是首次创建对象时调用的方法。 它们通常用于初始化对象的数据。
终结器C# 中很少使用终结器。 终结器是当对象即将从内存中移除时由运行时执行引擎调用的方法。 它们通常用来确保任何必须释放的资源都得到适当的处理。
嵌套类型嵌套类型是在其他类型中声明的类型。 嵌套类型通常用于描述仅由包含它们的类型使用的对象。

成员

常量

常量是不可变的值,在编译时是已知的,在程序的生命周期内不会改变。

常量使用 const 修饰符声明。

常量的特点:

  1. 必须初始化:在声明常量时,必须立即对其赋值。
  2. 编译时确定:常量的值在编译时确定,无法在运行时更改。
  3. 隐式静态:常量是隐式静态的,不需要使用 static 关键字。
  4. 只能使用简单类型:常量只能使用内置的基本数据类型(如 int、double、char 等),或者 string 类型。用户定义的类型(包括类、结构和数组)不能为 const。 使用 readonly 修饰符创建在运行时一次性(例如在构造函数中)初始化的类、结构或数组,此后不能更改。
  5. C# 不支持 const 方法、属性或事件。

示例:

C#
public class MathConstants
{
    public const double Pi = 3.14159;
    public const int MaxValue = 100;
}

//访问
Console.WriteLine(MathConstants.Pi);  // 输出: 3.14159

常量与 readonly 的区别

const:值在编译时确定,不能被修改,适用于编译时已知的值。

readonly:值可以在运行时确定,但只能在构造函数中赋值,之后不可修改。

特性constreadonly
初始化时间编译时运行时(可以在构造函数中初始化)
值是否可变只可在构造函数中赋值,之后不可修改
数据类型支持基本数据类型和 string任何类型
是否隐式静态否(但可以显式声明为静态)

readonly 示例:

C#
public class Config
{
    public readonly int MaxConnections;

    public Config(int maxConnections)
    {
        MaxConnections = maxConnections;
    }
}

字段和属性

在C#中,字段和属性是类或对象的数据成员,但它们的使用方式和目的有所不同。

字段是类或结构中直接存储数据的成员,通常用于存储对象的内部状态。

属性是对类中的字段提供访问的方法。它们提供了对字段的间接访问,允许控制对字段的读取和写入操作。通过属性,可以在获取或设置值时添加逻辑,比如验证或修改输入。

特性字段(Field)属性(Property)
访问方式直接访问通过 get 和 set 访问器间接访问
数据封装通常是私有的,直接存储数据提供对字段的控制访问,支持额外逻辑处理
访问控制通常只能控制访问修饰符可以通过 get 和 set 访问器控制读取与写入
自动实现需要手动声明并管理支持自动属性,由编译器自动生成字段
复杂逻辑不适合直接放置验证或计算逻辑可以在 set 或 get 中添加逻辑
字段

字段是类或结构中直接存储数据的成员,通常用于存储对象的内部状态。

特点:

  1. 字段通常是私有的 (private),避免外部类直接访问它们。
  2. 字段可以是任何类型,包括值类型和引用类型。
  3. 可以通过构造函数或直接赋值来初始化字段。

示例:

C#
public class Person
{
    private string name;
    private int age;

    public Person(string name, int age)
    {
        this.name = name;
        this.age = age;
    }

    public void DisplayInfo()
    {
        Console.WriteLine($"Name: {name}, Age: {age}");
    }
}
属性

属性是对类中的字段提供访问的方法。它们提供了对字段的间接访问,允许控制对字段的读取和写入操作。通过属性,可以在获取或设置值时添加逻辑,比如验证或修改输入。

  1. 属性使用 get 和 set 访问器来控制对字段的访问。
  2. get 访问器用于返回属性的值,set 访问器用于赋值。
  3. 属性可以是只读的(只有 get 访问器)或可读写的(有 get 和 set 访问器)。
  4. 属性可以通过自定义逻辑来控制赋值或获取值的方式。
C#
public class Person
{
    private string name;
    private int age;

    public string Name
    {
        get { return name; }
        set { name = value; }
    }

    public int Age
    {
        get { return age; }
        set 
        { 
            if (value >= 0)
                age = value;
        }
    }

    public void DisplayInfo()
    {
        Console.WriteLine($"Name: {Name}, Age: {Age}");
    }
}
自动属性(Auto-Implemented Properties)

自动属性提供了一种简化属性声明的方式,C# 编译器会自动为它们创建一个私有的匿名字段。在不需要添加额外逻辑时,可以使用自动属性。

C#
public class Person
{
    public string Name { get; set; }
    public int Age { get; set; }

    public void DisplayInfo()
    {
        Console.WriteLine($"Name: {Name}, Age: {Age}");
    }
}
required

在C#中,required 字段是从C# 11开始引入的一个新特性,允许你标记一个字段或属性为必需,即在对象初始化时必须赋值。

这个功能非常适合用于数据模型或POCO(Plain Old CLR Object)类,以确保在对象实例化时,所有必需的字段都得到了正确的初始化。

示例:

C#
public class Person
{
    public string Name { get; set; }
    public int Age { get; set; }

    public void DisplayInfo()
    {
        Console.WriteLine($"Name: {Name}, Age: {Age}");
    }
}

required 与构造函数的对比

  1. 使用 required 可以避免为每个类都创建多个不同参数的构造函数,简化代码编写和维护。
  2. 在对象初始化时,required 字段的赋值更加灵活,结合对象初始化器(Object Initializer)使代码更具可读性。

注意事项

  1. required 属性和字段的初始化顺序依赖于对象初始化器。
  2. required 字段仅在类中可用,不适用于结构体。
  3. 当 required 字段或属性在继承的类中时,派生类也必须初始化这些字段或属性。

方法

参考 方法

事件

参考 委托与事件

运算符

参考 运算符与表达式

索引器

索引器是C#中的一种特殊属性,允许对象像数组一样通过索引来访问内部元素。通过索引器,可以使用类似数组的语法来访问类或结构体中的集合,而无需显式调用方法。

索引器的作用是通过提供的索引来访问对象的集合元素(如数组、列表或字典等)。在定义索引器时,可以自定义访问的逻辑,因此它非常灵活。

示例:

C#
public class SampleCollection<T>
{
    private T[] arr = new T[100]; // 私有数组用于存储元素

    // 定义索引器
    public T this[int i]
    {
        get { return arr[i]; } // 返回指定索引的元素
        set { arr[i] = value; } // 设置指定索引的元素
    }
}

//使用
SampleCollection<string> stringCollection = new SampleCollection<string>();
stringCollection[0] = "Hello";
stringCollection[1] = "World";
Console.WriteLine(stringCollection[0]); // 输出: Hello
Console.WriteLine(stringCollection[1]); // 输出: World

多索引示例:

C#
public class Matrix
{
    private int[,] matrix = new int[3, 3];

    // 多维索引器
    public int this[int row, int col]
    {
        get { return matrix[row, col]; }
        set { matrix[row, col] = value; }
    }
}

//使用
Matrix matrix = new Matrix();
matrix[0, 0] = 1;
matrix[0, 1] = 2;
Console.WriteLine(matrix[0, 0]); // 输出: 1
Console.WriteLine(matrix[0, 1]); // 输出: 2

特点:

  1. 类似数组的语法:可以通过索引的方式访问和设置数据,而不需要调用方法。
  2. 可以自定义逻辑:在 get 和 set 访问器中可以添加自定义的访问逻辑,如验证或转换。
  3. 支持多个索引:索引器可以接受多个参数,这类似于多维数组。

应用场景

  1. 自定义集合类:通过索引器,可以使自定义的集合类(如列表、矩阵等)像数组一样操作。
  2. 字典或映射:在一些映射结构中,可以通过索引器轻松实现键值对的访问。

构造函数

C# 构造方法(Constructor)用于在创建对象时初始化对象的状态。

构造方法与类同名,不需要指定返回类型。在 C# 中,构造方法可以被重载,以提供不同的初始化方式。

默认构造方法

如果未显式定义构造方法,编译器会提供一个默认的无参数构造方法。

C#
public class Person
{
    // 默认构造方法,由编译器提供
}
无参数构造方法

你也可以手动定义无参数构造方法,用于自定义初始化。

C#
public class Person
{
    public string Name;

    public Person()
    {
        Name = "未知";
    }
}
带参数构造方法

允许在创建对象时传递参数,以初始化成员变量。

C#
public class Person
{
    public string Name;
    public int Age;

    public Person(string name, int age)
    {
        Name = name;
        Age = age;
    }
}
静态构造方法

用于初始化静态成员或执行仅需一次的操作。

静态构造方法在第一次创建实例或引用任何静态成员时被调用,且只能有一个,不能带访问修饰符和参数。

C#
public class Config
{
    public static readonly string ConnectionString;

    static Config()
    {
        ConnectionString = "Data Source=...";
    }
}
构造方法的重载

C#允许同一个类中定义多个构造方法,只要它们的参数列表不同。这种机制称为构造方法重载。

C#
public class Person
{
    public string Name { get; set; }
    public int Age { get; set; }

    // 无参构造方法
    public Person()
    {
        Name = "Unknown";
        Age = 0;
    }

    // 有参构造方法
    public Person(string name, int age)
    {
        Name = name;
        Age = age;
    }
}

嵌套类型(内部类型)

在 C# 中,嵌套类型指的是在一个类、结构体或接口内部定义的类型。

嵌套类型通常用于表示外部类型的某种内部概念,或者仅供外部类型使用的辅助类型。

嵌套类型可以是类、结构体、接口、枚举或委托。

特性;

  1. 不论外部类型是类、接口还是构造,嵌套类型均默认为 private;仅可从其包含类型中进行访问。
  2. “类”的嵌套类型可以是 public、protected、internal、protected internal、private 或 private protected。
  3. 在密封类中定义 protected、protected internal 或 private protected 嵌套类将产生编译器警告 CS0628“封闭类汇中声明了新的受保护成员”。
  4. 使嵌套类型在外部可见违反了代码质量规则 CA1034“嵌套类型不应是可见的”。
  5. 构造的嵌套类型可以是 public、internal 或 private。
  6. 嵌套类型(或内部类型)可访问包含类型(或外部类型)。 若要访问包含类型,请将其作为参数传递给嵌套类型的构造函数。
嵌套类

嵌套类是定义在另一个类内部的类。它可以访问外部类的成员(包括私有成员),并且通常用于封装外部类中的逻辑或提供一些内部服务。

C#
public class OuterClass
{
    private int outerValue = 100;

    // 嵌套类
    public class NestedClass
    {
        public void Display(OuterClass outer)
        {
            // 嵌套类可以访问外部类的私有成员
            Console.WriteLine(outer.outerValue);
        }
    }
}

//调用
OuterClass outer = new OuterClass();
OuterClass.NestedClass nested = new OuterClass.NestedClass();
nested.Display(outer);
嵌套结构体

你可以在类或其他结构体中定义结构体,这种嵌套类型结构体适合用于封装轻量级的值类型逻辑。

C#
public class Container
{
    public struct Dimensions
    {
        public int Width;
        public int Height;

        public Dimensions(int width, int height)
        {
            Width = width;
            Height = height;
        }
    }
}

Container.Dimensions dim = new Container.Dimensions(100, 200);
Console.WriteLine($"Width: {dim.Width}, Height: {dim.Height}");
嵌套接口

接口也可以嵌套在类或结构体中。嵌套接口通常用于定义外部类或结构体的内部行为。

C#
public class Engine
{
    // 嵌套接口
    public interface IStartable
    {
        void Start();
    }
}

public class Car : Engine.IStartable
{
    public void Start()
    {
        Console.WriteLine("Car started.");
    }
}

Car car = new Car();
car.Start();
嵌套枚举

枚举可以嵌套在类、结构体或接口中,表示外部类型的一组内部状态或常量值。

C#
public class Player
{
    // 嵌套枚举
    public enum PlayerState
    {
        Idle,
        Running,
        Jumping
    }

    public PlayerState State { get; set; }
}

Player player = new Player();
player.State = Player.PlayerState.Running;
Console.WriteLine(player.State);

表达式主体成员

表达式主体成员(Expression-bodied members)是C#中一种简化语法,用来定义简单的成员(如方法、属性、构造函数等)。

它通过 =>(lambda 运算符)来返回值或执行操作,适合于单行的逻辑处理,简化代码的书写和提高可读性。

表达式主体方法

表达式主体方法适用于返回值或包含简单的单行逻辑的函数。

C#
public class Calculator
{
    // 使用表达式主体定义一个方法
    public int Add(int a, int b) => a + b;
}
表达式主体属性

表达式主体属性可以用于简化只读属性的定义,尤其是当 get 访问器只有一行时。

C#
public class Person
{
    private string name;

    // 使用表达式主体定义只读属性
    public string Name => name;
}
表达式主体的 set 和 get 访问器

从C# 7.0开始,属性的 get 和 set 访问器也可以用表达式主体来定义。

示例:

C#
public class Person
{
    private string name;

    public string Name
    {
        get => name;            // 表达式主体的 get 访问器
        set => name = value;     // 表达式主体的 set 访问器
    }
}
表达式主体构造函数和析构函数

构造函数和析构函数也可以使用表达式主体的语法来简化实现。

C#
public class Person
{
    private string name;

    // 构造函数使用表达式主体
    public Person(string name) => this.name = name;

    // 析构函数
    ~Person() => Console.WriteLine("Destructor called");
}
表达式主体索引器

索引器也可以使用表达式主体语法。

C#
public class Collection
{
    private string[] data = { "A", "B", "C" };

    // 索引器使用表达式主体
    public string this[int index] => data[index];
}
表达式主体事件

从 C# 7.0 开始,事件的 add 和 remove 访问器也可以用表达式主体来定义。

C#
public class EventExample
{
    private event EventHandler myEvent;

    public event EventHandler MyEvent
    {
        add => myEvent += value;   // 使用表达式主体
        remove => myEvent -= value; // 使用表达式主体
    }
}

加载顺序

静态构造方法(子类) => 静态构造方法(父类)=> 实例构造方法(父类)=> 实例构造方法(子类)。

C#

public class Parent
{
    static Parent()
    {
        Console.WriteLine("父类静态构造方法被调用");
    }

    public Parent()
    {
        Console.WriteLine("父类实例构造方法被调用");
    }

}

public class Child : Parent
{
    static Child()
    {
        Console.WriteLine("子类静态构造方法被调用");
    }

    public Child() : base()
    {
        Console.WriteLine("子类实例构造方法被调用");
    }
}


class Program
{
    static void Main()
    {
        Child child = new Child();  // 创建子类实例
    }
}

//输出:
// 子类静态构造方法被调用
// 父类静态构造方法被调用
// 父类实例构造方法被调用
// 子类实例构造方法被调用

抽象类(abstract)

在 C# 中,抽象类(abstract class)是一种不能被直接实例化的类。

它可以包含抽象方法、普通方法、属性、字段和构造方法。

抽象类的主要目的是为派生类提供一个基本的框架,强制子类实现某些方法。

示例:

C#
public abstract class Animal
{
    protected string name;

    //构造方法
    protected Animal(string name)
    {
        this.name = name;
    }

    // 抽象方法,没有实现
    public abstract void MakeSound();

    // 虚方法,默认实现
    public virtual void Move()
    {
        Console.WriteLine(name + "Animal Move...");
    }

    // 具体方法,有实现
    public void Eat()
    {
        Console.WriteLine(name + "Animal Eating...");
    }
}

public class Dog : Animal
{
    // 构造方法
    public Dog(string name): base(name)
    {
        this.name = name;
    }

    // 实现抽象方法(必须)
    public override void MakeSound()
    {
        Console.WriteLine(name + "Dog MakeSound!");
    }

    // 不重写会有调用方法
    public override void Move()
    {
        Console.WriteLine(name + "Dog Move...");
        base.Move();
    }
}

class Program
{
    static void Main()
    {
        Dog dog = new Dog("张三 ");
        dog.MakeSound();    // 张三Dog MakeSound!
        dog.Move();         // 张三Dog Move... 张三Animal Move...
        dog.Eat();          // 张三Animal Eating...

        //Animal animal = new Animal("张三 "); 编译错误
    }
}

抽象类与接口的比较

抽象类:

  1. 可以包含字段、构造方法、属性和具体方法。
  2. 支持访问修饰符(如 private, protected, public)。
  3. 只能继承一个抽象类(单继承)。

接口:

  1. 只能包含方法签名、属性、事件和索引器,不可以有字段和实现。
  2. 方法默认是 public,接口中的成员不能有访问修饰符。
  3. 支持多重继承(一个类可以实现多个接口)。

抽象类的应用场景

  1. 定义模板:为子类提供通用的基类和基本实现。
  2. 强制实现:强制子类实现某些方法,从而遵循一定的契约。
  3. 封装共享代码:将共有的实现和数据封装在抽象类中,提高代码复用性。

abstract 方法 和 virtual 方法

abstract 方法:

  1. 是一种没有实现的纯虚方法,必须在派生类中实现。
  2. 只能在抽象类(abstract class)中定义。
  3. 不提供任何默认行为,必须由派生类进行实现。

virtual 方法:

  1. 是一种具有默认实现的方法,派生类可以选择是否重写它。
  2. 可以在任何非密封类中定义。
  3. 提供一个默认行为,但允许子类通过重写来修改或扩展该行为。

密封类(sealed)

在 C# 中,sealed 关键字用于防止类被继承,也可以用于防止虚方法被进一步重写。

密封类和密封方法都具有强制性的特点,目的是确保类或方法不再允许进一步扩展或修改。

密封类(Sealed Classes)

当一个类被声明为 密封类 时,其他类不能继承它。

这通常用于设计不能被扩展的类,确保类的实现不会被修改,特别是在安全性或完整性很重要的场合。

示例:

C#
public sealed class Calculator
{
    public int Add(int a, int b)
    {
        return a + b;
    }
}

// 错误示例
public class AdvancedCalculator : Calculator
{
    // 编译错误: 'Calculator' 类是密封的,不能被继承
}

密封方法(Sealed Methods)

示例:

C#
public class BaseClass
{
    // 定义虚方法,允许子类重写
    public virtual void Display()
    {
        Console.WriteLine("BaseClass Display");
    }
}

public class DerivedClass : BaseClass
{
    // 重写基类方法
    public override void Display()
    {
        Console.WriteLine("DerivedClass Display");
    }
}

public class MoreDerivedClass : DerivedClass
{
    // 重写并密封方法,防止进一步重写
    public sealed override void Display()
    {
        Console.WriteLine("MoreDerivedClass Display");
    }
}

// 错误示例
public class MostDerivedClass : MoreDerivedClass
{
    public override void Display()
    {
        // 编译错误: 无法重写密封方法 'MoreDerivedClass.Display'
    }
}

密封类的场景

  1. 安全性要求:类的实现涉及到安全性要求,或者其行为不能被修改。
  2. 性能优化:密封类可以在某些情况下提高运行时性能,因为编译器可以对其做出更多优化。
  3. 设计封闭:设计上不希望其他类继承或扩展该类的功能。

密封方法的场景

  1. 方法实现完整性:当基类中某些方法的重写在特定的派生类中已经达到期望效果,且不希望再被进一步修改时,可以密封它。
  2. 代码设计控制:控制某些核心逻辑或算法不被其他派生类篡改。

静态类(static)

在 C# 中,静态类(static class)是一种特殊的类,它的特点是只能包含静态成员。

静态类不能被实例化,也不能被继承。

这种类通常用于组织工具方法、全局状态或者不依赖于实例的功能。

特点:

  1. 只能包含静态成员:静态类的所有成员必须是静态的,包括字段、属性、方法、事件等。不能包含实例成员。
  2. 不能实例化:不能通过 new 关键字创建静态类的对象。
  3. 不能继承或被继承:静态类不能继承自其他类,也不能被继承。
  4. 隐式密封:静态类在设计上是密封的,即无法继承其他类或者被其他类继承。
  5. 效率高:由于静态类不需要实例化,直接通过类名访问,因此效率较高。常用于提供不依赖于对象实例的全局功能。
C#
public static class MathUtilities
{
    // 静态构造函数
    // 在第一次使用类时自动调用。静态构造函数不能带参数,且只能访问静态成员。
    static MathUtilities()
    {
        Console.WriteLine("Static constructor called.");
    }
    // 静态字段
    public static double Pi = 3.14159;

    // 静态方法
    public static double Square(double number)
    {
        return number * number;
    }
}

public class Program
{
    static void Main(string[] args)
    {
        // 直接通过类名访问静态成员
        double result = MathUtilities.Square(5);
        Console.WriteLine($"Square of 5 is: {result}"); // 输出: Square of 5 is: 25

        // 访问静态字段
        Console.WriteLine($"Value of Pi is: {MathUtilities.Pi}"); // 输出: Value of Pi is: 3.14159
    }
}

静态类 vs 普通类

特性静态类普通类
是否可以实例化不能实例化可以实例化
是否可以继承不能继承或被继承可以继承和被继承
成员类型只能包含静态成员可以包含静态和实例成员
访问方式通过类名访问静态成员通过实例访问实例成员,类名访问静态成员
主要用途提供全局工具方法或全局状态通常用于封装数据和行为的实例

分部类(partial)

在 C# 中,分部类(partial class)允许将一个类的定义分散到多个文件中。

通过使用 partial 关键字,你可以在多个文件中定义一个类、结构或接口。

这种功能通常用于组织大型代码文件,特别是在自动生成代码的情况下,手动编写的代码和自动生成的代码可以放在不同的文件中。

特点:

  1. 多个文件定义同一个类:一个类可以在多个文件中定义,只要每个部分都包含相同的访问修饰符,并且使用 partial 关键字。
  2. 编译时组合:尽管类定义分散在多个文件中,但在编译时,所有的部分会被组合成一个类。
  3. 一致的命名空间:所有分部类的定义必须位于相同的命名空间中。
  4. 支持字段、方法等的分离:可以将类的字段、属性、方法、事件等成员分散到不同的文件中进行定义。
  5. 常见于自动生成代码的场景:例如,使用 Visual Studio 自动生成代码时,设计器工具可能会创建一个 partial 类,开发者可以在另一个文件中编写额外的逻辑,而无需担心被自动生成的代码覆盖。

示例:

C#
// File1.cs:
public partial class Person
{
    // 字段和属性部分
    public string Name { get; set; }
    public int Age { get; set; }
}

// File2.cs:
public partial class Person
{
    // 方法部分
    public void DisplayInfo()
    {
        Console.WriteLine($"Name: {Name}, Age: {Age}");
    }
}

使用场景

  1. 代码组织:当一个类变得非常庞大时,可以使用分部类将代码逻辑分解到不同的文件中,方便管理和维护。
  2. 自动生成代码:在一些开发工具中(如 Visual Studio 的设计器工具),自动生成的代码通常会与手动编写的代码分开存放。
  3. 大型团队协作:在多人协作的项目中,开发者可以在不同的文件中对同一个类进行修改,而不会产生冲突。

规则和限制

  1. 必须在同一个命名空间中:所有分部类的定义必须在相同的命名空间中。
  2. 必须使用相同的访问修饰符:所有分部类的部分必须具有相同的访问级别(如 public、internal 等)。
  3. 静态和实例成员:分部类的不同部分可以包含静态和实例成员,也可以包含构造函数和析构函数。
  4. 类的完整性:即使类的定义被拆分到多个文件中,在编译时,编译器仍然会将它们视为同一个完整的类。因此,所有的成员都可以在整个类中进行访问,就像它们定义在同一个文件中一样。

分部方法 C# 中还支持 分部方法(partial method),允许你在一个文件中声明一个方法,而在另一个文件中定义它的实现。

C#
// File1.cs:
public partial class Person
{
    // 声明分部方法
    partial void OnNameChanged();
    
    public void ChangeName(string newName)
    {
        Name = newName;
        OnNameChanged();  // 调用分部方法
    }
}

// File2.cs:
public partial class Person
{
    // 实现分部方法
    partial void OnNameChanged()
    {
        Console.WriteLine($"Name has been changed to: {Name}");
    }
}

接口

在 C# 中,接口(interface)是一个定义了行为契约的类型,

它规定了类或结构必须实现的方法、属性、事件和索引器,但不提供具体的实现。

接口是实现多重继承、解耦和定义公共行为的核心工具。

特点

  1. 只定义行为,不提供实现:接口只定义了成员(如方法、属性、事件、索引器)的签名,不提供任何实际的实现。
  2. 可以被多个类实现:一个类可以实现多个接口,弥补了 C# 不支持多继承的缺陷。
  3. 多态性:接口可以用来实现多态性。通过接口引用,调用实现了该接口的对象的行为,而不关心其具体类型。
  4. 不能包含字段:接口不能包含实例字段,所有的成员必须是方法、属性、事件或索引器。
  5. 默认是 public:接口成员默认是 public,不能使用访问修饰符进行修饰。

示例:

C#
// 定义一个接口
public interface IAnimal
{
    // 接口成员
    void MakeSound();  // 方法
    string Name { get; set; }  // 属性
}

// 实现接口的类
public class Dog : IAnimal
{
    // 实现接口成员
    public string Name { get; set; }

    public void MakeSound()
    {
        Console.WriteLine("Dog barks.");
    }
}

public class Cat : IAnimal
{
    public string Name { get; set; }

    public void MakeSound()
    {
        Console.WriteLine("Cat meows.");
    }
}

class Program
{
    static void Main()
    {
        IAnimal dog = new Dog { Name = "Buddy" };
        IAnimal cat = new Cat { Name = "Whiskers" };

        dog.MakeSound();  // 输出: Dog barks.
        cat.MakeSound();  // 输出: Cat meows.
    }
}

多重接口实现

一个类可以实现多个接口,这为实现多重继承提供了灵活性。每个接口中的成员都必须实现。

示例:

C#
public interface IAnimal
{
    void MakeSound();
}

public interface IPet
{
    string Owner { get; set; }
    void Play();
}

public class Dog : IAnimal, IPet
{
    public string Owner { get; set; }

    public void MakeSound()
    {
        Console.WriteLine("Dog barks.");
    }

    public void Play()
    {
        Console.WriteLine("Dog plays fetch.");
    }
}

class Program
{
    static void Main()
    {
        Dog dog = new Dog { Owner = "John" };
        dog.MakeSound();  // 输出: Dog barks.
        dog.Play();       // 输出: Dog plays fetch.
    }
}

接口的新特性(C# 8.0+)

从 C# 8.0 开始,接口可以包含 默认实现。

这意味着接口不仅可以定义行为,还可以为某些方法提供默认的实现。

这给了接口更多的灵活性,但依然保持了与传统接口的兼容性。

示例(带有默认实现的接口):

C#
public interface IAnimal
{
    void MakeSound();

    // 默认实现
    void Sleep()
    {
        Console.WriteLine("The animal is sleeping.");
    }
}

public class Dog : IAnimal
{
    public void MakeSound()
    {
        Console.WriteLine("Dog barks.");
    }
}

class Program
{
    static void Main()
    {
        IAnimal dog = new Dog();
        dog.MakeSound();  // 输出: Dog barks.
        dog.Sleep();      // 输出: The animal is sleeping.
    }
}

Document Base on VitePress.