好的,我们来详细解释一下 C# 中的 public
和 protected
访问修饰符。
访问修饰符(Access Modifiers)用于指定类、方法、属性、字段等成员的可访问性级别,即哪些代码可以访问这些成员。
-
public (公开的)
- 含义:
public
是最宽松的访问级别。被public
修饰的成员(类、方法、属性等)可以从任何地方被访问,没有任何限制。 - 访问范围:
- 在声明它的类内部。
- 在同一程序集(Assembly)中的任何其他类。
- 在引用了该程序集的其他程序集中的任何类。
- 主要用途:
- 创建类的公共 API (Application Programming Interface),即你希望其他开发者或代码库使用的部分。
- 定义应用程序的入口点(例如
Main
方法通常是public static
)。 - 定义常量或工具方法,希望在整个应用程序或库中广泛使用。
示例:
// ----- Assembly A (MyLibrary.dll) ----- namespace MyLibrary { public class Calculator { public int Result { get; private set; } // Public property (getter is public, setter is private) public Calculator() // Public constructor { Result = 0; } public void Add(int number) // Public method { Result += number; } public void Subtract(int number) // Public method { Result -= number; } } public class Greeter { public string Greet(string name) { return $"Hello, {name}!"; } } } // ----- Assembly B (MyApp.exe, references MyLibrary.dll) ----- using MyLibrary; // So we don't have to write MyLibrary.Calculator using System; namespace MyApp { class Program { static void Main(string[] args) { Calculator calc = new Calculator(); // OK, Calculator class and constructor are public calc.Add(10); // OK, Add method is public calc.Add(5); Console.WriteLine($"Result: {calc.Result}"); // OK, Result property getter is public Greeter greeter = new Greeter(); Console.WriteLine(greeter.Greet("World")); // OK, Greet method is public } } }
- 含义:
-
protected (受保护的)
- 含义:
protected
成员只能在其声明的类内部,或者从该类派生的类(子类)内部被访问。 - 访问范围:
- 在声明它的类内部。
- 在从该类继承的任何派生类内部(无论派生类是否在同一个程序集中)。
- 关键点:
protected
成员对于“外部世界”(非派生类)是不可见的,就像private
一样。但它为子类提供了一个扩展或修改基类行为的“钩子”或“后门”。 - 主要用途:
- 允许派生类访问和修改基类的某些内部状态或行为,但又不想将这些细节完全公开给所有代码。
- 提供基类中可被子类重写(override)的方法的默认实现(通常与
virtual
关键字一起使用)。 - 定义辅助方法或字段,这些方法或字段对基类及其派生类有用,但对外部代码没有意义。
示例:
// ----- Assembly A (MyShapes.dll) ----- using System; namespace MyShapes { public abstract class Shape // Base class { protected string ShapeName { get; set; } // Protected property protected Shape(string name) // Protected constructor { ShapeName = name; Console.WriteLine($"Shape base constructor for: {ShapeName}"); } // Protected virtual method, intended to be overridden by derived classes protected virtual void DrawImplementation() { Console.WriteLine($"Drawing a generic {ShapeName}"); } public void Display() // Public method that uses the protected method { Console.Write($"Displaying {ShapeName}: "); DrawImplementation(); // Calls the (potentially overridden) protected method } } public class Circle : Shape // Derived class in the same assembly { public double Radius { get; set; } // Circle constructor calls the protected base constructor public Circle(double radius) : base("Circle") { Radius = radius; // We can access ShapeName here because it's protected and Circle derives from Shape Console.WriteLine($"Circle created with radius {Radius}. Name from base: {ShapeName}"); } // Override the protected virtual method protected override void DrawImplementation() { // Accessing protected member from base class Console.WriteLine($"Drawing a {ShapeName} with radius {Radius}"); } public void TestProtectedAccess(Circle otherCircle, Shape someShape) { // Accessing protected member of 'this' instance is fine Console.WriteLine(this.ShapeName); // Accessing protected member of another instance of the SAME derived type (Circle) is fine Console.WriteLine(otherCircle.ShapeName); // IMPORTANT: You CANNOT access a protected member of a base class instance // directly through a base class reference from a derived class, // UNLESS the access is through an instance of the current class or a class derived from it. // Console.WriteLine(someShape.ShapeName); // ERROR: 'Shape.ShapeName' is inaccessible due to its protection level // This is because 'someShape' might not be a 'Circle' or derived from 'Circle'. // The access must be through 'this', 'base', or an object of type 'Circle' (or derived from Circle). } } } // ----- Assembly B (MyDrawingApp.exe, references MyShapes.dll) ----- using MyShapes; // So we don't have to write MyShapes.Shape using System; namespace MyDrawingApp { public class Rectangle : Shape // Derived class in a different assembly { public double Width { get; set; } public double Height { get; set; } // Rectangle constructor calls the protected base constructor (from different assembly) public Rectangle(double width, double height) : base("Rectangle") { Width = width; Height = height; // ShapeName is accessible here because Rectangle derives from Shape Console.WriteLine($"Rectangle created. Name from base: {ShapeName}"); } // Override the protected virtual method protected override void DrawImplementation() { Console.WriteLine($"Drawing a {ShapeName} with width {Width} and height {Height}"); } } class Program { static void Main(string[] args) { Circle circle = new Circle(5); circle.Display(); // Calls public Display, which calls protected DrawImplementation Rectangle rectangle = new Rectangle(10, 4); rectangle.Display(); // Calls public Display, which calls protected DrawImplementation // Shape shape = new Shape("Generic"); // ERROR: 'Shape.Shape(string)' is inaccessible due to its protection level // Because the constructor is protected, you can't directly create Shape instances from outside // unless you are in a derived class constructor. // Console.WriteLine(circle.ShapeName); // ERROR: 'Shape.ShapeName' is inaccessible due to its protection level // ShapeName is protected, so it cannot be accessed from Program class // (Program does not derive from Shape) } } }
- 含义:
-
区别总结
特性 public
protected
访问级别 最高,无限制 仅限声明类及其派生类 谁能访问 任何代码(同一程序集、不同程序集) 声明该成员的类,以及从该类派生的子类(无论子类在哪个程序集) 主要目的 定义公共接口,供外部使用 允许子类扩展或修改基类行为,同时对外部隐藏实现细节。提供“继承契约”。 跨程序集 总是可以从其他程序集访问 派生类即使在不同程序集,也可以访问基类的 protected
成员。但非派生类(即使在同一程序集)不能访问。实例化 如果类和构造函数是 public
,则可以在任何地方实例化如果构造函数是 protected
,则只能在类自身内部或派生类的构造函数中通过base()
调用来创建实例,不能从外部直接new
。 -
何时使用
-
使用
public
:- 当你希望某个类或成员成为库或应用程序的稳定、公开的接口时。
- 当某个功能需要被项目中的任何其他部分调用时。
- 通常,应用程序的入口点、主要服务类、数据传输对象 (DTO) 的属性等会是
public
。
-
使用
protected
:- 当你设计一个基类,并希望子类能够访问或重写某些特定的行为或数据,但又不希望这些细节暴露给基类或子类的外部使用者时。
- 为子类提供可定制的“模板方法”或“钩子方法”。
- 当你想在继承层次结构中共享某些实现细节,但这些细节不应成为公共 API 的一部分。
设计原则:
- 封装 (Encapsulation):尽可能地限制成员的可见性。从
private
开始,如果需要被派生类访问,提升到protected
。只有当确实需要被外部完全访问时,才使用public
。 - 最小权限原则 (Principle of Least Privilege):一个成员应该只拥有完成其任务所必需的最低访问级别。
-
记住,选择正确的访问修饰符对于设计健壮、可维护和易于理解的软件系统至关重要。它们帮助你控制类的“表面积”,即暴露给外部世界的部分。