C#代理模式与委托机制详解及应用实践

📅 2026/7/19 21:35:44 👁️ 阅读次数 📝 编程学习
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 代理模式的五种常见类型

在实际开发中,代理模式有几种常见的变体:

  1. 虚拟代理(Virtual Proxy):延迟创建开销大的对象
  2. 保护代理(Protection Proxy):控制对原始对象的访问权限
  3. 远程代理(Remote Proxy):为位于不同地址空间的对象提供本地代表
  4. 智能引用代理(Smart Reference Proxy):在访问对象时执行附加操作
  5. 缓存代理(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#提供了几种内置的通用委托类型,可以避免重复定义相似的委托:

  1. Action:表示没有返回值的方法

    Action<string> print = Console.WriteLine; print("Hello World");
  2. Func:表示有返回值的方法

    Func<int, int, int> add = (x, y) => x + y; Console.WriteLine(add(3, 5)); // 输出8
  3. Predicate:表示返回bool的方法

    Predicate<int> isEven = x => x % 2 == 0; Console.WriteLine(isEven(4)); // 输出True

3. 代理模式与委托的实战应用

3.1 实现一个延迟加载代理

下面是一个完整的延迟加载代理实现,展示了如何结合代理模式和委托:

public interface IDataService { List<string> GetData(); } public class ExpensiveDataService : IDataService { public List<string> GetData() { // 模拟耗时操作 Thread.Sleep(3000); return new List<string> { "Data1", "Data2", "Data3" }; } } public class LazyDataServiceProxy : IDataService { private Lazy<List<string>> _data; private IDataService _realService; public LazyDataServiceProxy() { _realService = new ExpensiveDataService(); _data = new Lazy<List<string>>(() => _realService.GetData()); } public List<string> 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 EventHandler<OrderEventArgs> 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.CreateInterfaceProxyWithTarget<ICalculator>( new Calculator(), new LoggingInterceptor()); var result = calculator.Add(3, 5); Console.WriteLine($"Result: {result}");

4.2 委托性能优化技巧

  1. 避免频繁创建委托:委托是引用类型,频繁创建会导致GC压力

    // 不好:每次调用都创建新委托 for(int i = 0; i < 1000; i++) { Action action = () => Console.WriteLine(i); action(); } // 好:重用委托实例 Action<int> action = Console.WriteLine; for(int i = 0; i < 1000; i++) { action(i); }
  2. 使用静态方法:静态方法委托比实例方法委托性能更好

    public static class Formatter { public static string FormatDate(DateTime date) => date.ToString("yyyy-MM-dd"); } // 静态方法委托 Func<DateTime, string> formatter = Formatter.FormatDate;
  3. 多播委托的调用顺序:多播委托按添加顺序调用,使用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}"); } } } } }

陷阱3:lambda表达式捕获变量

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 何时使用代理模式

  1. 延迟初始化:当对象创建开销大时
  2. 访问控制:需要权限验证的场景
  3. 本地代表:与远程服务交互时
  4. 日志记录:需要记录方法调用时
  5. 缓存:需要缓存昂贵操作结果时

6.2 委托的最佳实践

  1. 使用内置委托类型:优先使用Action/Func/Predicate
  2. 事件模式标准化:遵循sender/args模式
  3. 明确委托用途:避免过度使用导致代码难以理解
  4. 注意生命周期:及时取消订阅避免内存泄漏
  5. 考虑性能:高频调用的场景避免委托开销

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 Dictionary<string, string> _cache = new(); public event Action<string> 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#中的代理模式和委托机制,可以显著提高代码的灵活性、可维护性和性能。关键是要根据具体场景选择合适的技术方案,并遵循最佳实践来避免常见的陷阱。