CSharp(三十)嵌套类(Nested Class)详解
目录
- 什么是嵌套类
- 为什么需要嵌套类
- 嵌套类的基本语法
- 访问规则详解
- public 嵌套类 vs private 嵌套类
- 嵌套类的访问修饰符
- 静态嵌套类 vs 实例嵌套类
- 多层嵌套
- 典型使用场景
- 嵌套类 vs 独立类
- 常见误区与注意事项
- 综合示例
1. 什么是嵌套类
嵌套类(Nested Class) 就是一个定义在另一个类内部的类。外层的类称为外部类(Outer Class),内层的类称为嵌套类。
通俗理解:把外部类比作一个"大盒子",嵌套类就是放在这个大盒子里的"小盒子"。这个小盒子虽然在物理上在大盒子里面,但它本身也是一个完整的类,可以有自己的字段、属性、方法。
┌─────────────────────────────────┐
│ 外部类 (Outer) │
│ │
│ ┌─────────────────────────┐ │
│ │ 嵌套类 (Inner) │ │
│ │ - 字段、属性、方法 │ │
│ │ - 构造函数 │ │
│ └─────────────────────────┘ │
│ │
└─────────────────────────────────┘
最简单的例子:
public class School // 外部类
{
public class Classroom // 嵌套类 — 定义在 School 内部
{
public string RoomNumber { get; set; }
public int Capacity { get; set; }
public void DisplayInfo()
{
Console.WriteLine($"教室 {RoomNumber},容纳 {Capacity} 人");
}
}
}
2. 为什么需要嵌套类
嵌套类最核心的用途是:将"只有外部类才用得着"的辅助类隐藏在外部类内部。
2.1 实际场景举例
假设你正在写一个 LinkedList(链表)类,链表内部需要一个 Node(节点)类来表示每个元素。这个 Node 类对于使用链表的开发者来说没有任何意义——他们只需要 LinkedList.Add()、LinkedList.Remove() 这些方法,完全不需要知道 Node 类的存在。
这时就可以把 Node 作为 LinkedList 的私有嵌套类:
public class LinkedList
{
// 使用者不需要知道 Node 的存在,所以用 private
private class Node
{
public int Value;
public Node Next;
public Node(int value)
{
Value = value;
Next = null;
}
}
private Node head; // 外部类像使用普通类型一样使用嵌套类
public void Add(int value)
{
Node newNode = new Node(value);
if (head == null)
{
head = newNode;
}
else
{
Node current = head;
while (current.Next != null)
current = current.Next;
current.Next = newNode;
}
}
public void PrintAll()
{
Node current = head;
while (current != null)
{
Console.Write(current.Value + " ");
current = current.Next;
}
Console.WriteLine();
}
}
// 使用链表时,你根本看不到 Node:
var list = new LinkedList();
list.Add(1);
list.Add(2);
list.Add(3);
list.PrintAll(); // 输出:1 2 3
2.2 使用嵌套类的好处
| 好处 | 说明 |
|---|---|
| 封装细节 | 把实现细节藏在外部类里面,外部调用者看不到,接口更干净 |
| 命名清晰 | LinkedList.Node 比 LinkedListNode 更能表达"这个类是专属于 LinkedList 的" |
| 避免命名冲突 | 两个不同的外部类可以有同名的私有嵌套类,互不影响 |
| 逻辑分组 | 相关代码放在一起,方便维护和理解 |
3. 嵌套类的基本语法
// 定义嵌套类的语法
public class 外部类
{
// 可以是 public、private、protected 等
public class 嵌套类
{
// 和普通类一模一样,可以写字段、属性、方法、构造函数等
}
}
3.1 完整示例
public class Order // 外部类:订单
{
public int OrderId { get; set; }
public string Customer { get; set; }
public decimal TotalAmount { get; private set; }
// 嵌套类:订单明细项
public class OrderItem
{
public string ProductName { get; set; }
public decimal Price { get; set; }
public int Quantity { get; set; }
// 嵌套类也可以有构造函数
public OrderItem(string productName, decimal price, int quantity)
{
ProductName = productName;
Price = price;
Quantity = quantity;
}
// 嵌套类可以有方法
public decimal GetSubTotal()
{
return Price * Quantity;
}
public override string ToString()
{
return $"{ProductName} × {Quantity} = {GetSubTotal():C}";
}
}
// 保存明细项的列表
private List<OrderItem> items = new List<OrderItem>();
// 外部类使用嵌套类
public void AddItem(string name, decimal price, int quantity)
{
items.Add(new OrderItem(name, price, quantity));
TotalAmount += price * quantity;
}
public void PrintOrder()
{
Console.WriteLine($"订单号:{OrderId},客户:{Customer}");
Console.WriteLine("明细:");
foreach (var item in items)
{
Console.WriteLine($" {item}");
}
Console.WriteLine($"总计:{TotalAmount:C}");
}
}
3.2 如何在外部使用嵌套类
如果嵌套类是 public 的,外部代码也可以创建嵌套类的实例,但需要通过外部类名来引用:
// ✅ 正确:通过"外部类.嵌套类"访问
Order.OrderItem item = new Order.OrderItem("键盘", 299, 2);
// ❌ 错误:不能直接使用嵌套类名
// OrderItem item = new OrderItem("键盘", 299, 2); // 这样会报错!
记忆技巧:嵌套类在全名是
外部类名.嵌套类名。就像文件路径一样,Order文件夹下的OrderItem文件,要写成Order.OrderItem。
4. 访问规则详解
这是嵌套类最容易混淆的地方,需要仔细理解。
4.1 规则一:嵌套类可以访问外部类的私有成员
嵌套类虽然是独立的类,但由于它"住"在外部类内部,所以嵌套类可以访问外部类的私有成员。
public class BankAccount
{
private decimal balance = 1000; // 外部类的 private 字段
private string secretKey = "ABC123"; // 外部类的 private 字段
// 嵌套类
public class AccountLogger
{
// 注意:这里需要传入外部类实例才能访问实例成员
public void LogBalance(BankAccount account)
{
// ✅ 可以!嵌套类能访问外部类的 private 成员
Console.WriteLine($"当前余额:{account.balance}");
Console.WriteLine($"密钥:{account.secretKey}");
}
}
public void Test()
{
var logger = new AccountLogger();
logger.LogBalance(this); // 传入当前实例
}
}
// 使用
var account = new BankAccount();
account.Test();
// 输出:
// 当前余额:1000
// 密钥:ABC123
重点:嵌套类访问的是外部类实例的私有成员,所以需要传入外部类的实例引用。静态成员则可以直接访问。
4.2 规则二:外部类可以访问嵌套类的私有成员
反过来,外部类也能访问嵌套类的私有成员(因为它们本质上在同一"家庭"里)。
public class Outer
{
private class Inner
{
private string secret = "内部秘密";
private int innerId = 42;
}
public void RevealSecret()
{
Inner inner = new Inner();
// ✅ 可以!外部类能访问嵌套类的 private 成员
Console.WriteLine($"我看到了:{inner.secret}");
Console.WriteLine($"ID:{inner.innerId}");
}
}
4.3 规则三:外界无法访问私有嵌套类
如果嵌套类是 private 的,外部世界完全看不到它,连创建实例都不行。
public class Outer
{
private class SecretHelper
{
public void DoWork()
{
Console.WriteLine("我在工作...");
}
}
// 外部类内部可以正常使用
public void UseHelper()
{
SecretHelper helper = new SecretHelper();
helper.DoWork();
}
}
// 外部使用:
var outer = new Outer();
outer.UseHelper(); // ✅ 可以,通过外部类间接使用
// SecretHelper helper = new Outer.SecretHelper(); // ❌ 编译错误!访问不到
4.4 访问规则速查表
| 访问方向 | public 嵌套类 | private 嵌套类 |
|---|---|---|
嵌套类 → 外部类的 private 成员 |
✅ | ✅ |
外部类 → 嵌套类的 private 成员 |
✅ | ✅ |
| 外界 → 嵌套类(直接创建实例) | ✅ | ❌ |
外界 → 嵌套类的 private 成员 |
❌ | ❌ |
同程序集的其他类 → 嵌套类的 internal 成员 |
✅ | ❌ |
5. public 嵌套类 vs private 嵌套类
这是使用嵌套类时最重要的选择:该用 public 还是 private?
5.1 private 嵌套类(最常用)
场景:嵌套类只是外部类的内部实现细节,外部调用者完全不需要知道。
public class MusicPlayer
{
// private 嵌套类:播放列表内部数据结构,用户不需要知道
private class SongNode
{
public string Title;
public string Artist;
public SongNode Previous;
public SongNode Next;
public SongNode(string title, string artist)
{
Title = title;
Artist = artist;
}
}
private SongNode current;
private SongNode first;
public void AddSong(string title, string artist)
{
SongNode node = new SongNode(title, artist);
if (first == null)
{
first = current = node;
}
else
{
SongNode last = first;
while (last.Next != null)
last = last.Next;
last.Next = node;
node.Previous = last;
}
}
public void Play()
{
if (current != null)
Console.WriteLine($"正在播放:{current.Title} - {current.Artist}");
}
}
// 使用者视角:完全看不到 SongNode,接口干净
var player = new MusicPlayer();
player.AddSong("夜曲", "周杰伦");
player.Play();
5.2 public 嵌套类
场景:嵌套类虽然逻辑上属于外部类,但外部代码也需要用到它。
public class Color
{
public int R { get; set; }
public int G { get; set; }
public int B { get; set; }
public Color(int r, int g, int b)
{
R = r; G = g; B = b;
}
// public 嵌套类:预定义颜色常量
// 外部代码可以用 Color.Preset.Red 来获取红色
public static class Preset
{
public static Color Red => new Color(255, 0, 0);
public static Color Green => new Color(0, 255, 0);
public static Color Blue => new Color(0, 0, 255);
public static Color Black => new Color(0, 0, 0);
public static Color White => new Color(255, 255, 255);
}
}
// 外部使用:
Color red = Color.Preset.Red; // 自然地通过 Color.Preset 访问
Color blue = Color.Preset.Blue;
Console.WriteLine($"红:R={red.R}"); // 红:R=255
5.3 选择建议
| 问题 | 用 private |
用 public |
|---|---|---|
| 嵌套类只是外部类的内部实现细节 | ✅ | - |
| 外部代码需要创建嵌套类的实例 | - | ✅ |
| 嵌套类有自己的公开 API | - | ✅ |
| 不确定该用哪个 | ✅ | - |
经验法则:有疑问时,先用
private。等真正有外部使用需求时再改成public,改起来很容易。
6. 嵌套类的访问修饰符
嵌套类可以拥有普通类不能使用的访问修饰符:
public class Outer
{
// ====== 嵌套类可以用这五种访问修饰符 ======
// 1. public:任何地方都能访问
public class PublicNested { }
// 2. private:只有外部类内部能访问(最常用)
private class PrivateNested { }
// 3. protected:外部类和派生类能访问
protected class ProtectedNested { }
// 4. internal:同一程序集内能访问
internal class InternalNested { }
// 5. protected internal:同一程序集或派生类能访问
protected internal class ProtectedInternalNested { }
}
注意:普通顶层类只能用
public和internal,而嵌套类因为坐在另一个类里面,所以可以用全部五种修饰符。
protected 嵌套类的实际应用
public class BaseController
{
// protected 嵌套类:只让派生类使用
protected class ValidationResult
{
public bool IsValid { get; set; }
public string ErrorMessage { get; set; }
public static ValidationResult Success()
=> new ValidationResult { IsValid = true };
public static ValidationResult Fail(string message)
=> new ValidationResult { IsValid = false, ErrorMessage = message };
}
}
// 派生类可以访问受保护的嵌套类
public class UserController : BaseController
{
public void CreateUser(string name, string email)
{
ValidationResult result;
if (string.IsNullOrEmpty(name))
result = ValidationResult.Fail("姓名不能为空");
else if (string.IsNullOrEmpty(email))
result = ValidationResult.Fail("邮箱不能为空");
else
result = ValidationResult.Success();
if (result.IsValid)
Console.WriteLine("用户创建成功");
else
Console.WriteLine($"创建失败:{result.ErrorMessage}");
}
}
// 外部类不能访问 protected 嵌套类
// var vr = new BaseController.ValidationResult(); // ❌ 编译错误!
7. 静态嵌套类 vs 实例嵌套类
7.1 实例嵌套类
不需要外部类实例就能创建,但它是一个普通类,有自己的实例。
public class Car
{
public string Model { get; set; }
// 实例嵌套类:需要创建自己的实例
public class Engine
{
public int HorsePower { get; set; }
public string Type { get; set; }
public void Start()
{
Console.WriteLine($"{HorsePower}马力 {Type} 引擎启动!");
}
}
}
// 使用:
Car.Engine engine = new Car.Engine(); // 不需要 Car 的实例
engine.HorsePower = 200;
engine.Type = "V6";
engine.Start(); // 输出:200马力 V6 引擎启动!
7.2 静态嵌套类
使用 static 修饰,只能包含静态成员,不需要实例化。
public class Calculator
{
// 静态嵌套类:全是静态方法,不需要 new
public static class Constants
{
public static double Pi => 3.1415926;
public static double E => 2.7182818;
public static double ConvertToDegrees(double radians)
{
return radians * 180 / Pi;
}
}
// 静态嵌套类作为分组工具
public static class Trigonometry
{
public static double Sin(double angle)
{
return Math.Sin(angle);
}
public static double Cos(double angle)
{
return Math.Cos(angle);
}
}
}
// 使用:不需要 new,直接用 外部类.嵌套类.方法
double area = Calculator.Constants.Pi * 5 * 5;
double degrees = Calculator.Constants.ConvertToDegrees(2 * Calculator.Constants.Pi);
double sinValue = Calculator.Trigonometry.Sin(Calculator.Constants.Pi / 2);
Console.WriteLine($"圆的面积:{area:F2}"); // 78.54
Console.WriteLine($"2π 弧度 = {degrees:F2}°"); // 360.00°
Console.WriteLine($"sin(π/2) = {sinValue}"); // 1
7.3 对照表
| 特性 | 实例嵌套类 | 静态嵌套类 |
|---|---|---|
| 声明方式 | public class Xxx |
public static class Xxx |
| 能否有实例字段 | ✅ 可以有 | ❌ 只能有静态字段 |
是否需要 new |
✅ 需要 | ❌ 不需要 |
| 能否有实例构造函数 | ✅ 可以有 | ❌ 不能有(静态类不能有构造函数) |
| 典型用途 | 辅助数据结构(Node, Item, Record 等) | 常量组、工具方法组、枚举值的逻辑分组 |
8. 多层嵌套
嵌套类内部还可以再嵌套类,不过一般不建议超过两层(超过两层会很难读):
public class Company
{
public string Name { get; set; }
// 第一层嵌套
public class Department
{
public string DeptName { get; set; }
// 第二层嵌套
public class Team
{
public string TeamName { get; set; }
public int MemberCount { get; set; }
public void Display()
{
Console.WriteLine($"团队:{TeamName},成员:{MemberCount}人");
}
}
}
}
// 使用时要一层一层写全名
var team = new Company.Department.Team();
team.TeamName = "后端开发组";
team.MemberCount = 8;
team.Display(); // 团队:后端开发组,成员:8人
建议:一般只用一层嵌套就够了。如果需要二层甚至三层,考虑是不是该重构为独立的类或使用命名空间来组织。
9. 典型使用场景
9.1 场景一:链表 / 树的节点
public class BinarySearchTree
{
// 树节点:外部使用者不需要知道树是怎么构成的
private class TreeNode
{
public int Value;
public TreeNode Left;
public TreeNode Right;
public TreeNode(int value)
{
Value = value;
Left = Right = null;
}
}
private TreeNode root;
public void Insert(int value)
{
root = InsertRec(root, value);
}
private TreeNode InsertRec(TreeNode node, int value)
{
if (node == null)
return new TreeNode(value);
if (value < node.Value)
node.Left = InsertRec(node.Left, value);
else if (value > node.Value)
node.Right = InsertRec(node.Right, value);
return node;
}
public bool Search(int value)
{
TreeNode current = root;
while (current != null)
{
if (value == current.Value)
return true;
current = value < current.Value ? current.Left : current.Right;
}
return false;
}
// 中序遍历(从小到大输出)
public void PrintInOrder()
{
InOrderTraversal(root);
Console.WriteLine();
}
private void InOrderTraversal(TreeNode node)
{
if (node != null)
{
InOrderTraversal(node.Left);
Console.Write(node.Value + " ");
InOrderTraversal(node.Right);
}
}
}
// 使用示例:
var bst = new BinarySearchTree();
bst.Insert(50);
bst.Insert(30);
bst.Insert(70);
bst.Insert(20);
bst.Insert(40);
bst.PrintInOrder(); // 20 30 40 50 70
Console.WriteLine(bst.Search(30)); // True
Console.WriteLine(bst.Search(100)); // False
9.2 场景二:Builder(构建器)模式
public class SqlQuery
{
public string SelectClause { get; private set; }
public string FromClause { get; private set; }
public string WhereClause { get; private set; }
public string OrderByClause { get; private set; }
// 构建器作为嵌套类
public class Builder
{
private SqlQuery query = new SqlQuery();
public Builder Select(string columns)
{
query.SelectClause = $"SELECT {columns}";
return this;
}
public Builder From(string table)
{
query.FromClause = $"FROM {table}";
return this;
}
public Builder Where(string condition)
{
query.WhereClause = $"WHERE {condition}";
return this;
}
public Builder OrderBy(string orderBy)
{
query.OrderByClause = $"ORDER BY {orderBy}";
return this;
}
public SqlQuery Build()
{
return query;
}
}
public string GetSql()
{
return $"{SelectClause} {FromClause} {WhereClause} {OrderByClause}".Trim();
}
}
// 使用:链式调用构建 SQL
var query = new SqlQuery.Builder()
.Select("Name, Age, Email")
.From("Users")
.Where("Age > 18")
.OrderBy("Name ASC")
.Build();
Console.WriteLine(query.GetSql());
// 输出:SELECT Name, Age, Email FROM Users WHERE Age > 18 ORDER BY Name ASC
9.3 场景三:枚举或常量的逻辑分组
public class GameSettings
{
// 用静态嵌套类分组相关常量
public static class Audio
{
public static float MasterVolume = 1.0f;
public static float MusicVolume = 0.8f;
public static float SfxVolume = 1.0f;
public static void Mute()
{
MasterVolume = 0;
Console.WriteLine("已静音");
}
}
public static class Graphics
{
public static int ResolutionWidth = 1920;
public static int ResolutionHeight = 1080;
public static int FrameRate = 60;
public static bool Vsync = true;
public static string GetResolution()
{
return $"{ResolutionWidth}×{ResolutionHeight}@{FrameRate}fps";
}
}
public static class Controls
{
public static float MouseSensitivity = 1.5f;
public static bool InvertY = false;
}
}
// 使用:清晰的层级结构
GameSettings.Audio.Mute();
Console.WriteLine(GameSettings.Graphics.GetResolution()); // 1920×1080@60fps
GameSettings.Controls.MouseSensitivity = 2.0f;
9.4 场景四:结果包装类
public class NetworkRequest
{
// 用嵌套类封装请求结果,外部使用时语义清晰
public class Result
{
public bool Success { get; set; }
public string Data { get; set; }
public int StatusCode { get; set; }
public string ErrorMessage { get; set; }
public static Result Ok(string data)
=> new Result { Success = true, Data = data, StatusCode = 200 };
public static Result Fail(int statusCode, string error)
=> new Result { Success = false, StatusCode = statusCode, ErrorMessage = error };
}
public Result Fetch(string url)
{
// 模拟网络请求
if (string.IsNullOrEmpty(url))
return Result.Fail(400, "URL 不能为空");
if (url.Contains("error"))
return Result.Fail(500, "服务器内部错误");
return Result.Ok($"从 {url} 获取的数据...");
}
}
// 使用:
var request = new NetworkRequest();
var result1 = request.Fetch("https://api.example.com/users");
if (result1.Success)
Console.WriteLine($"成功:{result1.Data}");
else
Console.WriteLine($"失败 [{result1.StatusCode}]:{result1.ErrorMessage}");
var result2 = request.Fetch("");
if (!result2.Success)
Console.WriteLine($"失败 [{result2.StatusCode}]:{result2.ErrorMessage}");
10. 嵌套类 vs 独立类
什么时候用嵌套类?
| 情况 | 用嵌套类 | 用独立类 |
|---|---|---|
| 辅助类只在一个类内部使用 | ✅ | - |
| 辅助类和主类有紧密逻辑关系 | ✅ | - |
| 想对外隐藏实现细节 | ✅ | - |
| 辅助类需要被多个类共享使用 | - | ✅ |
| 辅助类逻辑复杂,单独维护更好理解 | - | ✅ |
| 辅助类可能在未来被其他模块复用 | - | ✅ |
| 为了用嵌套类作为命名空间组织代码(Java 风格) | - | ✅ |
核心判断标准:问自己"这个辅助类离开外部类还有独立价值吗?"如果答案是"没有",就用嵌套类。
11. 常见误区与注意事项
误区一:嵌套类会自动持有外部类实例
public class Outer
{
public string Name = "外部";
public int Value = 100;
public class Inner
{
public void Test()
{
// ❌ 错误!Inner 没有自动持有 Outer 的引用
// Console.WriteLine(Name); // 编译错误:Name 不存在
// Console.WriteLine(Value); // 编译错误:Value 不存在
}
}
}
C# 的嵌套类和 Java 的"内部类"不同。Java 的非静态内部类会自动持有外部类引用,但 C# 的嵌套类不会。在 C# 中要访问外部类实例成员,必须显式传入外部类引用。
public class Outer
{
public string Name = "外部";
public class Inner
{
// ✅ 正确:通过参数接收外部类实例
public void Test(Outer outer)
{
Console.WriteLine(outer.Name); // 可以访问了
}
}
}
误区二:嵌套类可以访问外部类的实例成员(没有实例引用)
public class Outer
{
private int instanceField = 42;
private static int staticField = 100;
public class Inner
{
public void Demo()
{
// ❌ 错误:不能直接访问实例成员
// Console.WriteLine(instanceField);
// ✅ 可以:静态成员属于类本身,不需要实例
Console.WriteLine($"静态字段:{staticField}");
}
public void Demo2(Outer outer)
{
// ✅ 可以:有实例引用就能访问
Console.WriteLine($"实例字段:{outer.instanceField}");
}
}
}
误区三:过度使用嵌套类
// ❌ 不好的做法:三层甚至更多层的嵌套,代码很难读
public class A
{
public class B
{
public class C
{
public class D // 太难读了!
{
public void DoSomething() { }
}
}
}
}
// 使用时:
var d = new A.B.C.D();
d.DoSomething(); // 虽然能工作,但层级太深让人头晕
注意事项总结
- C# 嵌套类不会自动持有外部类引用,和 Java 不同
- 嵌套类只能访问外部类的静态成员(不传引用的前提下)
- 要访问外部类的实例成员,必须显式传入外部类实例
- 嵌套层数一般不超过 2 层
- 所有五种访问修饰符都可以用于嵌套类
12. 综合示例
下面是一个完整的配置管理系统示例,综合运用了本文介绍的各种技巧:
using System;
using System.Collections.Generic;
public class AppConfig
{
// 外部类的私有字段
private Dictionary<string, string> settings = new Dictionary<string, string>();
// ======== 静态嵌套类:配置键名常量 ========
public static class Keys
{
public const string AppName = "AppName";
public const string Version = "Version";
public const string MaxUsers = "MaxUsers";
public const string EnableLogging = "EnableLogging";
}
// ======== 静态嵌套类:默认值 ========
public static class Defaults
{
public const string AppName = "MyApplication";
public const string Version = "1.0.0";
public const string MaxUsers = "100";
public const string EnableLogging = "true";
}
// ======== 私有嵌套类:配置校验器(对外隐藏)========
private class Validator
{
public static bool ValidateMaxUsers(string value, out int result)
{
if (int.TryParse(value, out result) && result > 0)
return true;
result = 0;
return false;
}
public static bool ValidateVersion(string value)
{
// 简单校验版本号格式 x.y.z
return System.Text.RegularExpressions.Regex.IsMatch(
value, @"^\d+\.\d+\.\d+$");
}
}
// ======== public 嵌套类:配置读取结果 ========
public class ReadResult
{
public bool Found { get; set; }
public string Value { get; set; }
public bool IsValid { get; set; }
public string ErrorMessage { get; set; }
public static ReadResult NotFound()
=> new ReadResult { Found = false };
public static ReadResult Ok(string value)
=> new ReadResult { Found = true, Value = value, IsValid = true };
public static ReadResult Invalid(string value, string error)
=> new ReadResult { Found = true, Value = value, IsValid = false, ErrorMessage = error };
}
// ======== 外部类方法 ========
/// <summary>
/// 设置配置项
/// </summary>
public void Set(string key, string value)
{
settings[key] = value;
}
/// <summary>
/// 读取并校验配置项
/// </summary>
public ReadResult Get(string key)
{
if (!settings.TryGetValue(key, out string value))
return ReadResult.NotFound();
// 使用私有嵌套类做校验
switch (key)
{
case Keys.MaxUsers:
if (!Validator.ValidateMaxUsers(value, out _))
return ReadResult.Invalid(value, "MaxUsers 必须是正整数");
break;
case Keys.Version:
if (!Validator.ValidateVersion(value))
return ReadResult.Invalid(value, "Version 格式必须为 x.y.z");
break;
}
return ReadResult.Ok(value);
}
/// <summary>
/// 加载默认配置
/// </summary>
public void LoadDefaults()
{
Set(Keys.AppName, Defaults.AppName);
Set(Keys.Version, Defaults.Version);
Set(Keys.MaxUsers, Defaults.MaxUsers);
Set(Keys.EnableLogging, Defaults.EnableLogging);
Console.WriteLine("默认配置已加载");
}
/// <summary>
/// 打印所有配置
/// </summary>
public void PrintAll()
{
Console.WriteLine("\n========== 当前配置 ==========");
foreach (var kv in settings)
{
Console.WriteLine($" {kv.Key} = {kv.Value}");
}
Console.WriteLine("===============================\n");
}
}
// ========== 测试代码 ==========
class Program
{
static void Main()
{
var config = new AppConfig();
// 加载默认配置
config.LoadDefaults();
config.PrintAll();
// 修改部分配置
config.Set(AppConfig.Keys.AppName, "无敌学习系统");
config.Set(AppConfig.Keys.MaxUsers, "500");
// 读取并校验
CheckConfig(config, AppConfig.Keys.AppName);
CheckConfig(config, AppConfig.Keys.MaxUsers);
CheckConfig(config, AppConfig.Keys.Version);
// 故意设置一个非法的值来测试校验
config.Set(AppConfig.Keys.Version, "不合法版本");
CheckConfig(config, AppConfig.Keys.Version);
// 测试不存在的配置项
CheckConfig(config, "不存在的键名");
}
static void CheckConfig(AppConfig config, string key)
{
AppConfig.ReadResult result = config.Get(key);
if (!result.Found)
{
Console.WriteLine($"[{key}] 未找到");
}
else if (!result.IsValid)
{
Console.WriteLine($"[{key}] = {result.Value} (❌ 不合法:{result.ErrorMessage})");
}
else
{
Console.WriteLine($"[{key}] = {result.Value} (✅)");
}
}
}
运行结果:
默认配置已加载
========== 当前配置 ==========
AppName = MyApplication
Version = 1.0.0
MaxUsers = 100
EnableLogging = true
===============================
[AppName] = 无敌学习系统 (✅)
[MaxUsers] = 500 (✅)
[Version] = 1.0.0 (✅)
[Version] = 不合法版本 (❌ 不合法:Version 格式必须为 x.y.z)
[不存在的键名] 未找到
小结
| 要点 | 说明 |
|---|---|
| 什么是嵌套类 | 定义在另一个类内部的类,全名为 外部类.嵌套类 |
| 为什么用嵌套类 | 封装内部实现细节、避免命名冲突、逻辑分组 |
| private 嵌套类(推荐首选) | 仅外部类内部使用,对外隐藏实现 |
| public 嵌套类 | 外部代码通过 Outer.Inner 方式使用 |
| 访问规则 | 嵌套类可访问外部类私有成员(需外部类实例);外部类也可访问嵌套类私有成员 |
| C# vs Java | C# 嵌套类不会自动持有外部类引用,需显式传入 |
| 静态嵌套类 | 用于常量分组、工具方法分组,用 static class 声明 |
| 嵌套层数 | 建议不超过 2 层,过深难维护 |
一句话总结:嵌套类就是把"只给某个类打工"的辅助类藏在该类里面,让代码更整洁、更安全。
评论区