你是否在C#开发中遇到过这样的场景:需要临时修改某个配置或状态,执行一段业务逻辑后,再将其恢复到原始状态?比如临时切换数据库连接、修改日志级别、调整缓存策略等。
传统做法往往需要手动保存原值、设置新值,最后在 finally
块中恢复,代码繁琐且容易出错。今天给大家介绍一个优雅的解决方案:ScopedValueChanger 工具类,让临时值管理变得简单安全!
在实际项目中,我们经常需要:
C#// 传统繁琐写法
var originalLevel = Logger.Level;
try
{
Logger.Level = LogLevel.Debug; // 临时修改
// 执行需要调试级别的逻辑
DoSomething();
}
finally
{
Logger.Level = originalLevel; // 手动恢复
}
问题显而易见:
利用C#的 using
语句和 IDisposable
接口,实现作用域级别的自动值管理:
C#using System;
namespace AppScopedValueChanger
{
public class ScopedValueChanger<T> : IDisposable
{
private readonly Func<T> _getter;
private readonly Action<T> _setter;
private readonly T _originalValue;
public event EventHandler<ValueChangedEventArgs<T>> ValueChanged;
// 创建一个新的临时值变更器实例
public ScopedValueChanger(Func<T> getter, Action<T> setter, T newValue)
{
_getter = getter ?? throw new ArgumentNullException(nameof(getter));
_setter = setter ?? throw new ArgumentNullException(nameof(setter));
_originalValue = getter(); // 保存原值
// 触发变更事件
ValueChanged?.Invoke(this, new ValueChangedEventArgs<T>(_originalValue, newValue, false));
setter(newValue); // 设置新值
}
public void Dispose()
{
// 触发恢复事件
ValueChanged?.Invoke(this, new ValueChangedEventArgs<T>(_getter(), _originalValue, true));
// 恢复原始值
_setter(_originalValue);
}
}
public class ValueChangedEventArgs<T> : EventArgs
{
public T OldValue { get; }
public T NewValue { get; }
public bool IsRestoring { get; }
public ValueChangedEventArgs(T oldValue, T newValue, bool isRestoring)
{
OldValue = oldValue;
NewValue = newValue;
IsRestoring = isRestoring;
}
}
}
C#using System.Reflection.Emit;
namespace AppScopedValueChanger1
{
public enum LogLevel
{
Trace,
Debug,
Info,
Warn,
Error,
Fatal
}
public class Logger
{
public static LogLevel Level { get; set; } = LogLevel.Info;
public static void Log(string message)
{
Console.WriteLine($"[{Level}] {message}");
}
}
internal class Program
{
static void Main(string[] args)
{
// 使用 ScopedValueChanger 的优雅写法
using (new ScopedValueChanger<LogLevel>(
() => Logger.Level, // 获取当前值
level => Logger.Level = level, // 设置新值
LogLevel.Debug)) // 临时值
{
// 在这个作用域内,日志级别为 Debug
Logger.Log("详细调试信息");
}
// 自动恢复到原来的 Info 级别
Logger.Log("一般信息");
}
}
}
C#using System.Reflection.Emit;
namespace AppScopedValueChanger1
{
public class DatabaseConfig
{
public static string ConnectionString { get; set; } = "ProductionDB";
}
internal class Program
{
static void Main(string[] args)
{
// 测试环境临时切换
using (new ScopedValueChanger<string>(
() => DatabaseConfig.ConnectionString,
conn => DatabaseConfig.ConnectionString = conn,
"TestDB"))
{
// 在此作用域内使用测试数据库
Console.WriteLine($"Current Connection String: {DatabaseConfig.ConnectionString}");
}
// 自动切回生产数据库
Console.WriteLine($"Restored Connection String: {DatabaseConfig.ConnectionString}");
}
}
}
C#using System;
using System.Threading.Tasks;
using System.Windows.Forms;
namespace WinFormsApp1
{
public partial class Form1 : Form
{
public Form1()
{
InitializeComponent();
}
private async Task ProcessDataAsync()
{
// 这里必须在 UI 线程上创建 ScopedValueChanger,
// 因为它会直接操作控件属性(btnProcess.Enabled)
using (new ScopedValueChanger<bool>(
() => btnProcess.Enabled,
enabled => btnProcess.Enabled = enabled,
false))
{
try
{
// 模拟耗时异步操作(例如网络请求、IO 等)
await Task.Delay(3000);
}
catch (Exception ex)
{
// 处理异常(可选),按钮恢复由 Dispose 自动完成
MessageBox.Show($"发生错误:{ex.Message}", "错误", MessageBoxButtons.OK, MessageBoxIcon.Error);
}
}
// 离开 using 块时,btnProcess.Enabled 会自动恢复为原始值
}
private async void btnProcess_Click(object sender, EventArgs e)
{
if (!btnProcess.Enabled) return;
await ProcessDataAsync();
}
}
}
C#var changer = new ScopedValueChanger<int>(
() => GlobalCounter.Value,
val => GlobalCounter.Value = val,
100);
// 监听值变化
changer.ValueChanged += (sender, args) =>
{
if (args.IsRestoring)
Console.WriteLine($"恢复值:{args.NewValue}");
else
Console.WriteLine($"设置值:{args.OldValue} → {args.NewValue}");
};
1. 避免嵌套使用同一属性
C#// ❌ 错误用法 - 可能导致恢复值混乱
using (new ScopedValueChanger<int>(() => Config.Value, v => Config.Value = v, 10))
{
using (new ScopedValueChanger<int>(() => Config.Value, v => Config.Value = v, 20))
{
// 内层结束时恢复到10,外层结束时恢复到原值
}
}
2. 确保 getter/setter 的原子性
C#// ✅ 正确用法 - 使用线程安全的属性
private static readonly object _lock = new object();
private static int _value;
public static int ThreadSafeValue
{
get { lock (_lock) return _value; }
set { lock (_lock) _value = value; }
}
C#public class AsyncScopedValueChanger<T> : IAsyncDisposable
{
// 支持异步的 getter/setter
private readonly Func<Task<T>> _asyncGetter;
private readonly Func<T, Task> _asyncSetter;
// ... 实现异步版本
}
C#public class ConfigurationScope : IDisposable
{
private readonly List<IDisposable> _changers = new();
public ConfigurationScope Set<T>(Func<T> getter, Action<T> setter, T value)
{
_changers.Add(new ScopedValueChanger<T>(getter, setter, value));
return this;
}
public void Dispose() => _changers.ForEach(c => c.Dispose());
}
通过 ScopedValueChanger<T>
工具类,我们实现了:
🛡️ 安全性:利用 using
语句确保值必定恢复,避免状态污染
🎯 简洁性:一行代码解决临时值管理,告别繁琐的 try-finally
🔍 可观测性:内置事件机制,便于调试和监控值的变化过程
这个工具类虽然简单,但在实际项目中能显著提升代码质量和维护效率。特别是在单元测试、配置管理、状态切换等场景下,堪称**"收藏级"的代码模板**!
💭 互动时间:
觉得这个技巧有用的话,请转发给更多C#同行,让更多开发者写出更优雅的代码!🚀
关注我,获取更多C#开发实战技巧和最佳实践分享!
本文作者:技术老小子
本文链接:
版权声明:本博客所有文章除特别声明外,均采用 BY-NC-SA 许可协议。转载请注明出处!