一、访问修饰符的控制范围
1.public:完全公开
- 访问范围 :无限制。任何代码,无论是在同一个程序集(Assembly)还是在其他程序集中,都可以访问被
public修饰的成员。 - 适用对象:类、结构、接口、枚举、委托、以及类的成员(方法、属性、字段等)。
- 用途:用于定义一个类的"公共API"。这些是你希望提供给外部世界使用的功能。
csharp
// 在 AssemblyA.dll 中
public class Calculator
{
public int Add(int a, int b) // 任何代码都可以调用这个方法
{
return a + b;
}
}
csharp
// 在另一个项目 AssemblyB.exe 中,引用了 AssemblyA.dll
using AssemblyA;
class Program
{
static void Main(string[] args)
{
Calculator calc = new Calculator();
int result = calc.Add(2, 3); // 可以访问
Console.WriteLine(result);
}
}
2.private:绝对私有
- 访问范围 :仅限在声明它的类或结构内部。
- 适用对象 :类的成员(嵌套类、方法、属性、字段等)。注意:顶层类型(非嵌套类)不能是
private的。 - 用途 :封装的基石。用于隐藏类的内部状态和实现细节。一个类中绝大部分的字段都应该是
private的。
csharp
public class BankAccount
{
private decimal _balance; // 只能在BankAccount类内部访问
public void Deposit(decimal amount)
{
if (amount > 0)
{
_balance += amount; // 内部可以访问
}
}
}
class Test
{
void DoSomething()
{
BankAccount account = new BankAccount();
// account._balance = 10000; // 编译错误!无法从外部访问_balance
}
}
3.protected:家族内部的秘密
- 访问范围 :在声明它的类内部,以及**该类的任何子类(派生类)**中。
- 适用对象:类的成员。
- 用途:为了让子类能够访问和扩展父类的功能,同时又不想让这些功能对外界完全公开。它在继承体系中扮演着关键角色。
csharp
// 在 AssemblyA.dll 中
public class Animal
{
protected string Name { get; set; } // 子类可以访问Name
protected void Eat()
{
Console.WriteLine($"{Name} is eating.");
}
}
public class Dog : Animal
{
public Dog(string name)
{
this.Name = name; // 正确:子类可以访问受保护的成员
}
public void Bark()
{
Console.WriteLine("Woof!");
this.Eat(); // 正确:子类可以调用受保护的方法
}
}
csharp
// 在 AssemblyA.dll 或其他程序集中
class Test
{
void DoSomething()
{
Animal animal = new Animal();
// animal.Name = "Tom"; // 编译错误!外部无法访问protected成员
// animal.Eat(); // 编译错误!
Dog dog = new Dog("Buddy");
// dog.Name = "Lucy"; // 编译错误!即使通过子类实例,外部也无法访问
}
}
关键点 :protected的访问权限是基于继承关系 的,而不是实例。在Test类中,即使有一个Dog的实例,也不能访问其protected成员,因为Test类不是Animal的子类。
4. nternal:同一个"项目"里的朋友
- 访问范围:仅限在同一个**程序集(Assembly)**内部。一个程序集通常对应一个项目(.csproj),编译后生成一个DLL或EXE文件。
- 适用对象:顶层类型和类的成员。
- 用途 :当你想在同一个项目或组件内部共享一些类或工具方法,但又不希望将它们暴露给引用这个组件的外部项目时,
internal是最佳选择。这是创建框架或库时非常有用的一个修饰符。
csharp
// 在 AssemblyA.dll 中
internal class InternalHelper
{
public static void DoWork()
{
Console.WriteLine("Internal work done.");
}
}
public class PublicFacade
{
public void PerformAction()
{
InternalHelper.DoWork(); // 同一个程序集内,可以访问internal类
}
}
csharp
// 在另一个项目 AssemblyB.exe 中,引用了 AssemblyA.dll
using AssemblyA;
class Program
{
static void Main(string[] args)
{
PublicFacade facade = new PublicFacade();
facade.PerformAction(); // 可以调用
// InternalHelper helper = new InternalHelper(); // 编译错误!InternalHelper在AssemblyB中不可见
}
}
默认访问级别 :如果你在声明一个顶层类(非嵌套)时不写任何访问修饰符 ,它的默认访问级别就是internal。
csharp
// 等同于 internal class MyClass { }
class MyClass { }
5. 两种组合修饰符:更精细的控制
C#还提供了两种由protected和internal组合而成的修饰符,用于实现更复杂的访问控制场景。
5.1 protected internal:家族朋友
- 访问范围 :满足以下任一条件 即可访问:
- 在同一个程序集内。
- 在不同的程序集中,但必须是该类的子类。
- 逻辑关系 :
protectedORinternal。这是最宽松的访问级别之一(仅次于public)。 - 用途:当你希望一个成员主要在程序集内部使用,但同时也允许其他程序集中的子类对其进行扩展时。
csharp
// 在 AssemblyA.dll 中
public class BaseClass
{
protected internal int Value { get; set; }
}
public class DerivedInSameAssembly : BaseClass
{
void Test()
{
this.Value = 10; // 可以访问,因为在同一个程序集
}
}
internal class OtherClassInSameAssembly
{
void Test()
{
BaseClass b = new BaseClass();
b.Value = 20; // 可以访问,因为在同一个程序集
}
}
csharp
// 在 AssemblyB.exe 中,引用了 AssemblyA.dll
using AssemblyA;
public class DerivedInOtherAssembly : BaseClass
{
void Test()
{
this.Value = 30; // 可以访问,因为是子类
}
}
class OtherClassInOtherAssembly
{
void Test()
{
BaseClass b = new BaseClass();
// b.Value = 40; // 编译错误!不在同一程序集,也不是子类
}
}
5.2 private protected:本家的私密
- 访问范围 :必须同时满足 以下两个条件才能访问:
- 在同一个程序集内。
- 必须是该类的子类。
- 逻辑关系 :
privateANDprotected(概念上),或者更准确地说是protectedANDinternal。 - 用途 :这是最严格的访问级别之一。当你希望某个成员只能被同一个项目中的子类访问时使用。这可以防止其他不相关的项目继承你的类并滥用这些受保护的成员。
csharp
// 在 AssemblyA.dll 中
public class BaseClass
{
private protected string SecretCode { get; set; }
}
public class DerivedInSameAssembly : BaseClass
{
void Test()
{
this.SecretCode = "Alpha"; // 可以访问,同一程序集 + 子类
}
}
internal class OtherClassInSameAssembly
{
void Test()
{
BaseClass b = new BaseClass();
// b.SecretCode = "Beta"; // 编译错误!虽然在同一程序集,但不是子类
}
}
csharp
// 在 AssemblyB.exe 中,引用了 AssemblyA.dll
using AssemblyA;
public class DerivedInOtherAssembly : BaseClass
{
void Test()
{
// this.SecretCode = "Gamma"; // 编译错误!虽然是子类,但不在同一个程序集
}
}
三、访问修饰符总结与对比
| 修饰符 | 当前类内部 | 同程序集内的子类 | 同程序集内的非子类 | 不同程序集的子类 | 不同程序集的非子类 |
|---|---|---|---|---|---|
public |
✅ | ✅ | ✅ | ✅ | ✅ |
protected internal |
✅ | ✅ | ✅ | ✅ | ❌ |
internal |
✅ | ✅ | ✅ | ❌ | ❌ |
protected |
✅ | ✅ | ❌ | ✅ | ❌ |
private protected |
✅ | ✅ | ❌ | ❌ | ❌ |
private |
✅ | ❌ | ❌ | ❌ | ❌ |
结语
点个赞,关注我获取更多实用 C# 技术干货!如果觉得有用,记得收藏本文