-
静态类与单例模式——用C#开发全局配置管理器
第一部分:C#基础入门
实例5:静态类与单例模式——全局配置管理器
实例介绍
在软件系统中,全局配置(如数据库连接字符串、API密钥、系统开关)是核心资源,需要满足两个关键需求:
1.全局可访问:任何模块都能快速获取配置值;
2.唯一实例:避免重复加载配置(浪费资源)或多个实例导致配置不一致。
静态类和单例模式是实现这两个需求的常用手段,但vb.net教程C#教程python教程SQL教程access 2010教程适用场景不同:
静态类:适合无状态工具类(如配置读取辅助方法),无需实例化,直接通过类名访问;
单例模式:适合有状态且需唯一实例的对象(如配置管理器),能控制实例创建时机,支持延迟加载。
本实例通过实现一个全局配置管理器,带你掌握静态类的使用、单例模式的线程安全实现,以及两者的核心区别,写出高效、安全的全局资源管理代码。
需求分析
1.静态工具类:ConfigHelper(静态类),提供配置文件读取的辅助方法(模拟从JSON文件读取);
2.单例配置管理器:GlobalConfigManager(单例类),实现:
o加载配置(从静态工具类获取或内存模拟);
o获取配置值(如GetConnectionString());
o刷新配置(重新加载最新值);
3.线程安全:单例模式需支持多线程环境下安全获取实例;
4.对比验证:测试静态类和单例的使用场景,明确各自优势。
代码实现
csharp
using System;
using System.Collections.Generic;
using System.Threading;
namespace GlobalConfigDemo
{
// 1. 静态工具类:配置读取辅助(无状态,仅提供工具方法)
public static class ConfigHelper
{
// 静态方法:模拟从JSON文件读取配置(返回键值对)
public static Dictionary<string, string> ReadConfigFromFile(string filePath)
{
// 模拟文件读取(实际项目用Newtonsoft.Json等库)
Console.WriteLine($"[ConfigHelper] 读取配置文件:{filePath}");
return new Dictionary<string, string>
{
{"DbConnection", "Server=localhost;Database=DemoDB;Uid=root;Pwd=123456;"},
{"ApiKey", "sk_abc123xyz456"},
{"MaxRetryCount", "3"}
};
}
// 静态方法:转换配置值为整数(工具方法)
public static int ParseInt(string value, int defaultValue)
{
if (int.TryParse(value, out int result))
return result;
return defaultValue;
}
}
// 2. 单例模式:全局配置管理器(有状态,唯一实例)
public sealed class GlobalConfigManager // sealed防止子类继承,破坏单例
{
// 2.1 私有静态变量:存储唯一实例(volatile确保多线程可见性)
private static volatile GlobalConfigManager _instance;
// 2.2 私有静态锁:线程安全控制
private static readonly object _lockObj = new object();
// 2.3 私有配置字典:存储配置值(有状态)
private Dictionary<string, string> _configs;
// 2.4 私有构造函数:禁止外部实例化
private GlobalConfigManager()
{
// 初始化:从静态工具类读取配置
_configs = ConfigHelper.ReadConfigFromFile("appsettings.json");
Console.WriteLine("[GlobalConfigManager] 实例创建成功,配置已加载");
}
// 2.5 公共静态方法:获取唯一实例(双重检查锁定,线程安全+延迟加载)
public static GlobalConfigManager GetInstance()
{
// 第一次检查:无锁,快速返回已创建实例
if (_instance == null)
{
// 加锁:确保只有一个线程进入实例化逻辑
lock (_lockObj)
{
// 第二次检查:防止多个线程等待锁后重复实例化
if (_instance == null)
{
_instance = new GlobalConfigManager();
}
}
}
return _instance;
}
// 2.6 公共方法:获取配置值
public string GetConfig(string key)
{
if (_configs.TryGetValue(key, out string value))
return value;
return null;
}
// 2.7 公共方法:刷新配置(重新读取文件)
public void RefreshConfig()
{
Console.WriteLine("[GlobalConfigManager] 刷新配置...");
_configs = ConfigHelper.ReadConfigFromFile("appsettings.json");
}
// 2.8 公共方法:获取整数类型配置(调用静态工具类)
public int GetIntConfig(string key, int defaultValue)
{
string value = GetConfig(key);
return ConfigHelper.ParseInt(value, defaultValue);
}
}
// 3. 主程序:测试静态类与单例模式
class Program
{
static void Main(string[] args)
{
// 测试静态类:直接通过类名调用,无需实例化
Console.WriteLine("
=== 测试静态类ConfigHelper ===");
var configFromFile = ConfigHelper.ReadConfigFromFile("appsettings.json");
foreach (var kv in configFromFile)
Console.WriteLine($"{kv.Key}: {kv.Value}");
int maxRetry = ConfigHelper.ParseInt("3", 1);
Console.WriteLine($"转换整数:{maxRetry}");
// 测试单例模式:获取唯一实例
Console.WriteLine("
=== 测试单例GlobalConfigManager ===");
// 第一次获取:创建实例
var configManager1 = GlobalConfigManager.GetInstance();
// 第二次获取:返回已有实例(无新实例创建日志)
var configManager2 = GlobalConfigManager.GetInstance();
// 验证实例是否唯一
Console.WriteLine($"实例是否唯一:{ReferenceEquals(configManager1, configManager2)}");
// 测试配置获取
Console.WriteLine("
=== 获取配置值 ===");
string dbConn = configManager1.GetConfig("DbConnection");
string apiKey = configManager1.GetConfig("ApiKey");
int maxRetryCount = configManager1.GetIntConfig("MaxRetryCount", 1);
Console.WriteLine($"数据库连接串:{dbConn}");
Console.WriteLine($"API密钥:{apiKey}");
Console.WriteLine($"最大重试次数:{maxRetryCount}");
// 测试刷新配置
Console.WriteLine("
=== 刷新配置 ===");
configManager1.RefreshConfig();
string newDbConn = configManager1.GetConfig("DbConnection");
Console.WriteLine($"刷新后数据库连接串:{newDbConn}");
}
}
}
逐行讲解
-
静态类ConfigHelper
public static class ConfigHelper:static关键字标记为静态类,不能实例化(无构造函数),所有成员必须是静态的;
public static Dictionary<string, string> ReadConfigFromFile(...):静态方法,直接通过类名调用,无需创建实例;
public static int ParseInt(...):静态工具方法,无状态(不依赖实例变量),适合封装通用逻辑;
静态类特点:默认密封(sealed),不能继承;静态成员属于类而非实例,生命周期与应用程序一致。 -
单例类GlobalConfigManager
sealed class GlobalConfigManager:sealed防止子类继承,避免通过子类构造函数创建新实例,破坏单例;
private static volatile GlobalConfigManager _instance:volatile确保多线程环境下实例变量的可见性(防止编译器优化导致的线程不可见问题);
private static readonly object _lockObj:静态锁对象,用于线程安全控制;
private GlobalConfigManager():私有构造函数,禁止外部通过new创建实例,确保唯一;
public static GlobalConfigManager GetInstance():双重检查锁定逻辑:
第一次检查:无锁快速返回,提升性能;
加锁:确保只有一个线程进入实例化流程;
第二次检查:防止多个线程等待锁后重复创建实例;
private Dictionary<string, string> _configs:有状态成员,存储配置值,单例实例持有唯一的配置数据。 -
主程序测试逻辑
静态类调用:直接用ConfigHelper.ReadConfigFromFile(...),无需实例化;
单例实例获取:两次调用GetInstance()返回同一实例,验证唯一性;
配置操作:获取配置值、刷新配置,体现单例的有状态特性。
基础知识拓展 - 静态类vs单例模式核心区别
| 特性 | 静态类(Static Class) 单例模式(Singleton) |
|---|---|
| 实例化 | 不能实例化,无构造函数 私有构造函数,唯一实例 |
| 状态 | 无状态(静态成员共享,无实例变量) 有状态(实例变量存储数据) |
| 继承 | 不能继承(默认密封) 可继承(但通常密封防止破坏单例) |
| 生命周期 | 应用程序启动时加载,直到结束 延迟加载(第一次调用时创建) |
| 线程安全 | 静态成员默认线程安全(但需注意共享数据修改) 需手动实现线程安全(如双重检查) |
| 适用场景 | 无状态工具类(如配置读取、字符串处理) 有状态全局唯一实例(如配置管理器、日志器) |
-
单例模式的常见实现方式
| 实现方式 | 特点 | 线程安全 | 延迟加载 |
| ---- | ---- | ---- | ---- |
| 饿汉式 |静态初始化实例,类加载时创建 |✅(CLR保证静态初始化线程安全) | ❌(提前加载,浪费资源) |
| 懒汉式(无锁) | 第一次调用时创建 |❌(多线程下重复实例化) | ✅ |
| 双重检查锁定 | 两次检查+锁 | ✅ | ✅ |
| Lazy(C#内置) | 利用Lazy 类,简单安全 | ✅(CLR保证) | ✅ |
推荐实现:C#中优先使用Lazy
csharp
public sealed class GlobalConfigManager
{
private static readonly Lazy<GlobalConfigManager> _lazyInstance =
new Lazy<GlobalConfigManager>(() => new GlobalConfigManager());
public static GlobalConfigManager Instance => _lazyInstance.Value;
private GlobalConfigManager() { ... }
}
-
静态类的注意事项
避免滥用静态类:静态成员生命周期长,易导致内存泄漏(如静态集合未清空);
静态类无状态:不要在静态类中存储可变共享数据(如静态字典),否则多线程修改会导致线程安全问题;
静态类的初始化:静态成员在第一次访问类时初始化,顺序由编译器决定,避免依赖静态成员的初始化顺序。
扩展思考
1.配置持久化:将_configs持久化到JSON文件,实现配置修改后保存到磁盘;
2.配置监听:添加IObservable接口,当配置刷新时通知订阅者(如数据库连接池重新连接);
3.依赖注入中的单例:在ASP.NET Core中,使用services.AddSingleton()注册单例,替代手动实现的单例模式;
4.多线程安全刷新:在RefreshConfig()方法中加锁,防止多线程同时刷新导致配置不一致;
5.静态类的替代方案:用依赖注入的瞬态服务(Transient)替代静态工具类,提升可测试性(如Mock配置读取)。
总结
静态类和单例模式都是实现全局访问的有效手段,但需根据场景选择:
静态类:适合封装无状态的通用工具方法(如字符串处理、文件读取),无需实例化,简单高效;
单例模式:适合有状态的全局唯一实例(如配置管理器、日志服务),能控制实例生命周期,支持延迟加载;
在实际项目中,优先使用C#内置的Lazy实现单例,避免手动锁的复杂性;静态类则应限制在无状态工具类的场景,避免滥用导致全局状态混乱。通过合理使用这两种模式,可写出高效、可维护的全局资源管理代码。
本站原创,转载请注明出处:https://www.xin3721.com/ArticlecSharp/c49406.html
最新更新
扩展方法——用C#编写字符串工具类增强
静态类与单例模式——用C#开发全局配置
封装与访问修饰符——用C#开发用户隐私
接口与抽象类——用C#开发支付方式统一
继承与多态——用C#开发动物分类与行为
控制台高级交互——用C#开发菜单驱动式
类与对象——用C#开发图书管理系统基础
枚举与结构体——用C#开发订单状态管理
c#中关于Nullable类型与空合并——用户信息
异常处理——用C#文件读写错误捕获与恢
SQL SERVER中递归
2个场景实例讲解GaussDB(DWS)基表统计信息估
常用的 SQL Server 关键字及其含义
动手分析SQL Server中的事务中使用的锁
openGauss内核分析:SQL by pass & 经典执行
一招教你如何高效批量导入与更新数据
天天写SQL,这些神奇的特性你知道吗?
openGauss内核分析:执行计划生成
[IM002]Navicat ODBC驱动器管理器 未发现数据
初入Sql Server 之 存储过程的简单使用
uniapp/H5 获取手机桌面壁纸 (静态壁纸)
[前端] DNS解析与优化
为什么在js中需要添加addEventListener()?
JS模块化系统
js通过Object.defineProperty() 定义和控制对象
这是目前我见过最好的跨域解决方案!
减少回流与重绘
减少回流与重绘
如何使用KrpanoToolJS在浏览器切图
performance.now() 与 Date.now() 对比










