C#代理模式与委托机制详解及应用实践
1. C#代理模式深度解析在C#开发中代理这个概念实际上包含两个重要层面一是设计模式中的代理模式(Proxy Pattern)二是语言特性中的委托(Delegate)。很多初学者容易混淆这两者但它们在实际开发中都有着不可替代的作用。1.1 代理模式的核心思想代理模式属于结构型设计模式它为其他对象提供一种代理以控制对这个对象的访问。这种模式在实际项目中应用广泛特别是在需要控制对原始对象的访问时非常有用。典型的代理模式包含三个关键角色Subject抽象主题定义真实主题和代理主题的共同接口RealSubject真实主题实现真正的业务逻辑Proxy代理持有对真实主题的引用控制对真实主题的访问// 抽象主题 public interface IImage { void Display(); } // 真实主题 public class RealImage : IImage { private string _fileName; public RealImage(string fileName) { _fileName fileName; LoadFromDisk(); } private void LoadFromDisk() { Console.WriteLine($Loading {_fileName}); } public void Display() { Console.WriteLine($Displaying {_fileName}); } } // 代理 public class ProxyImage : IImage { private RealImage _realImage; private string _fileName; public ProxyImage(string fileName) { _fileName fileName; } public void Display() { if(_realImage null) { _realImage new RealImage(_fileName); } _realImage.Display(); } }1.2 代理模式的五种常见类型在实际开发中代理模式有几种常见的变体虚拟代理(Virtual Proxy)延迟创建开销大的对象保护代理(Protection Proxy)控制对原始对象的访问权限远程代理(Remote Proxy)为位于不同地址空间的对象提供本地代表智能引用代理(Smart Reference Proxy)在访问对象时执行附加操作缓存代理(Cache Proxy)为开销大的运算结果提供临时存储2. C#委托(Delegate)机制详解2.1 委托的本质与声明委托是C#中的一种类型它可以安全地封装方法类似于C中的函数指针但更加类型安全。委托的核心价值在于它允许将方法作为参数传递。声明委托的基本语法public delegate returnType DelegateName(parameterList);实际示例// 声明一个委托类型 public delegate void LogHandler(string message); // 使用委托 public class Logger { public void WriteToConsole(string message) { Console.WriteLine($Console: {message}); } public void WriteToFile(string message) { File.AppendAllText(log.txt, ${DateTime.Now}: {message}\n); } } // 客户端代码 var logger new Logger(); LogHandler logDelegate logger.WriteToConsole; logDelegate logger.WriteToFile; logDelegate(Application started);2.2 内置委托类型C#提供了几种内置的通用委托类型可以避免重复定义相似的委托Action表示没有返回值的方法Actionstring print Console.WriteLine; print(Hello World);Func表示有返回值的方法Funcint, int, int add (x, y) x y; Console.WriteLine(add(3, 5)); // 输出8Predicate表示返回bool的方法Predicateint isEven x x % 2 0; Console.WriteLine(isEven(4)); // 输出True3. 代理模式与委托的实战应用3.1 实现一个延迟加载代理下面是一个完整的延迟加载代理实现展示了如何结合代理模式和委托public interface IDataService { Liststring GetData(); } public class ExpensiveDataService : IDataService { public Liststring GetData() { // 模拟耗时操作 Thread.Sleep(3000); return new Liststring { Data1, Data2, Data3 }; } } public class LazyDataServiceProxy : IDataService { private LazyListstring _data; private IDataService _realService; public LazyDataServiceProxy() { _realService new ExpensiveDataService(); _data new LazyListstring(() _realService.GetData()); } public Liststring GetData() { return _data.Value; } } // 使用示例 var proxy new LazyDataServiceProxy(); Console.WriteLine(Proxy created, real service not initialized yet); // 第一次访问会初始化真实服务 var data proxy.GetData(); Console.WriteLine($Data loaded: {string.Join(, , data)});3.2 委托在事件系统中的应用委托在C#事件系统中扮演着核心角色。下面是一个完整的事件系统实现示例// 定义事件参数 public class OrderEventArgs : EventArgs { public string OrderId { get; set; } public DateTime OrderTime { get; set; } } // 发布者 public class OrderProcessor { // 定义基于委托的事件 public event EventHandlerOrderEventArgs OrderProcessed; public void ProcessOrder(string orderId) { // 处理订单逻辑... Thread.Sleep(1000); // 触发事件 OnOrderProcessed(new OrderEventArgs { OrderId orderId, OrderTime DateTime.Now }); } protected virtual void OnOrderProcessed(OrderEventArgs e) { OrderProcessed?.Invoke(this, e); } } // 订阅者 public class NotificationService { public void SendConfirmation(object sender, OrderEventArgs e) { Console.WriteLine($Sending confirmation for order {e.OrderId}); } } // 使用示例 var processor new OrderProcessor(); var notifier new NotificationService(); // 订阅事件 processor.OrderProcessed notifier.SendConfirmation; // 处理订单会触发通知 processor.ProcessOrder(ORD12345);4. 高级应用与性能优化4.1 动态代理实现AOP使用Castle DynamicProxy可以实现面向切面编程(AOP)// 需要安装Castle.Core NuGet包 public class LoggingInterceptor : IInterceptor { public void Intercept(IInvocation invocation) { Console.WriteLine($Before {invocation.Method.Name}); var watch Stopwatch.StartNew(); invocation.Proceed(); // 调用实际方法 watch.Stop(); Console.WriteLine($After {invocation.Method.Name}, took {watch.ElapsedMilliseconds}ms); } } public interface ICalculator { int Add(int x, int y); } public class Calculator : ICalculator { public int Add(int x, int y) x y; } // 创建动态代理 var generator new ProxyGenerator(); var calculator generator.CreateInterfaceProxyWithTargetICalculator( new Calculator(), new LoggingInterceptor()); var result calculator.Add(3, 5); Console.WriteLine($Result: {result});4.2 委托性能优化技巧避免频繁创建委托委托是引用类型频繁创建会导致GC压力// 不好每次调用都创建新委托 for(int i 0; i 1000; i) { Action action () Console.WriteLine(i); action(); } // 好重用委托实例 Actionint action Console.WriteLine; for(int i 0; i 1000; i) { action(i); }使用静态方法静态方法委托比实例方法委托性能更好public static class Formatter { public static string FormatDate(DateTime date) date.ToString(yyyy-MM-dd); } // 静态方法委托 FuncDateTime, string formatter Formatter.FormatDate;多播委托的调用顺序多播委托按添加顺序调用使用GetInvocationList可以控制调用Action multi () Console.WriteLine(First); multi () Console.WriteLine(Second); // 手动控制调用顺序 foreach(Action handler in multi.GetInvocationList().Reverse()) { handler(); }5. 常见问题与解决方案5.1 代理模式常见问题问题1代理导致性能下降症状使用代理后系统响应变慢解决方案对于频繁访问的对象考虑使用轻量级代理实现缓存机制减少重复计算评估是否真的需要代理模式问题2代理与真实对象不一致症状代理行为与真实对象表现不同解决方案确保代理正确转发所有方法调用使用接口约束代理和真实对象的行为实现全面的单元测试5.2 委托使用中的陷阱陷阱1委托引用导致内存泄漏public class Publisher { public event EventHandler SomethingHappened; } public class Subscriber { public Subscriber(Publisher pub) { pub.SomethingHappened HandleEvent; } private void HandleEvent(object sender, EventArgs e) { Console.WriteLine(Event handled); } } // 使用示例 var pub new Publisher(); var sub new Subscriber(pub); // 即使sub不再被引用pub仍然持有对sub的引用 // 解决方案实现IDisposable或在适当时候取消订阅陷阱2多线程环境下的委托调用public class EventExample { public event EventHandler Updated; public void Update() { // 不安全的调用方式 // Updated?.Invoke(this, EventArgs.Empty); // 线程安全的调用方式 var handlers Updated; if(handlers ! null) { foreach(EventHandler handler in handlers.GetInvocationList()) { try { handler(this, EventArgs.Empty); } catch(Exception ex) { // 处理单个handler的异常 Console.WriteLine($Handler failed: {ex.Message}); } } } } }陷阱3lambda表达式捕获变量for(int i 0; i 5; i) { Task.Run(() Console.WriteLine(i)); } // 可能输出5个5而不是0-4 // 解决方案使用临时变量 for(int i 0; i 5; i) { int temp i; Task.Run(() Console.WriteLine(temp)); }6. 实际项目中的应用建议6.1 何时使用代理模式延迟初始化当对象创建开销大时访问控制需要权限验证的场景本地代表与远程服务交互时日志记录需要记录方法调用时缓存需要缓存昂贵操作结果时6.2 委托的最佳实践使用内置委托类型优先使用Action/Func/Predicate事件模式标准化遵循sender/args模式明确委托用途避免过度使用导致代码难以理解注意生命周期及时取消订阅避免内存泄漏考虑性能高频调用的场景避免委托开销6.3 混合使用代理和委托的案例下面是一个结合代理模式和委托的缓存实现public interface IDataFetcher { string FetchData(string key); } public class RemoteDataFetcher : IDataFetcher { public string FetchData(string key) { // 模拟远程调用 Thread.Sleep(1000); return $Data for {key}; } } public class CachingProxy : IDataFetcher { private readonly IDataFetcher _realFetcher; private readonly Dictionarystring, string _cache new(); public event Actionstring CacheMiss; public CachingProxy(IDataFetcher realFetcher) { _realFetcher realFetcher; } public string FetchData(string key) { if(_cache.TryGetValue(key, out var data)) { return data; } CacheMiss?.Invoke(key); data _realFetcher.FetchData(key); _cache[key] data; return data; } } // 使用示例 var fetcher new CachingProxy(new RemoteDataFetcher()); fetcher.CacheMiss key Console.WriteLine($Cache miss for {key}); // 第一次调用会触发CacheMiss事件 Console.WriteLine(fetcher.FetchData(user1)); // 第二次调用从缓存获取 Console.WriteLine(fetcher.FetchData(user1));在实际项目中理解并正确运用C#中的代理模式和委托机制可以显著提高代码的灵活性、可维护性和性能。关键是要根据具体场景选择合适的技术方案并遵循最佳实践来避免常见的陷阱。