C# 中 public 和 protected 的区别和用法

好的,我们来详细解释一下 C# 中的 publicprotected 访问修饰符。

访问修饰符(Access Modifiers)用于指定类、方法、属性、字段等成员的可访问性级别,即哪些代码可以访问这些成员。

  1. 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
            }
        }
    }
    
  2. 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)
            }
        }
    }
    
  3. 区别总结

    特性 public protected
    访问级别 最高,无限制 仅限声明类及其派生类
    谁能访问 任何代码(同一程序集、不同程序集) 声明该成员的类,以及从该类派生的子类(无论子类在哪个程序集)
    主要目的 定义公共接口,供外部使用 允许子类扩展或修改基类行为,同时对外部隐藏实现细节。提供“继承契约”。
    跨程序集 总是可以从其他程序集访问 派生类即使在不同程序集,也可以访问基类的 protected 成员。但非派生类(即使在同一程序集)不能访问。
    实例化 如果类和构造函数是 public,则可以在任何地方实例化 如果构造函数是 protected,则只能在类自身内部或派生类的构造函数中通过 base() 调用来创建实例,不能从外部直接 new
  4. 何时使用

    • 使用 public

      • 当你希望某个类或成员成为库或应用程序的稳定、公开的接口时。
      • 当某个功能需要被项目中的任何其他部分调用时。
      • 通常,应用程序的入口点、主要服务类、数据传输对象 (DTO) 的属性等会是 public
    • 使用 protected

      • 当你设计一个基类,并希望子类能够访问或重写某些特定的行为或数据,但又不希望这些细节暴露给基类或子类的外部使用者时。
      • 为子类提供可定制的“模板方法”或“钩子方法”。
      • 当你想在继承层次结构中共享某些实现细节,但这些细节不应成为公共 API 的一部分。

    设计原则

    • 封装 (Encapsulation):尽可能地限制成员的可见性。从 private 开始,如果需要被派生类访问,提升到 protected。只有当确实需要被外部完全访问时,才使用 public
    • 最小权限原则 (Principle of Least Privilege):一个成员应该只拥有完成其任务所必需的最低访问级别。

记住,选择正确的访问修饰符对于设计健壮、可维护和易于理解的软件系统至关重要。它们帮助你控制类的“表面积”,即暴露给外部世界的部分。