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

相关新闻

ESP32 I2S音频开发与优化实战

ESP32 I2S音频开发与优化实战

1. ESP32 I2S音频开发核心解析在嵌入式音频开发领域,ESP32凭借其双核处理能力和丰富的外设接口,成为了低成本音频项目的首选方案。我最近完成了一个基于INMP441麦克风和PCM5102解码器的语音采集系统,实测采样率可达16kHz/16bit,完…

2026/10/13 17:14:50 阅读更多 →
042-Form Kit服务卡片

042-Form Kit服务卡片

042 — Form Kit 服务卡片:从 2x2 到 4x4 的桌面数据展示实践 简介 服务卡片是鸿蒙系统的标志性能力之一,它让用户无需打开应用就能在桌面上快速查看关键信息。MoneyTrack 利用 Form Kit 实现了两种尺寸的桌面卡片:2x2 的迷你卡片&#xff…

2026/10/12 10:10:20 阅读更多 →
Android构建变体:Gradle多版本管理实战指南

Android构建变体:Gradle多版本管理实战指南

1. 构建变体的本质与价值在Android应用开发中,构建变体(Build Variant)是Gradle构建系统的核心概念之一。简单来说,构建变体就是通过不同维度的组合,生成具有差异化特性的应用版本。这就像一家汽车工厂,用相…

2026/10/8 4:45:03 阅读更多 →

最新新闻

EA4eig:病态矩阵高精度特征值求解的MATLAB稳定器

EA4eig:病态矩阵高精度特征值求解的MATLAB稳定器

简介:本资源是面向算法研究者与MATLAB初/中级使用者的EA4eig单目标数值优化算法实现包,专为CEC 2022基准测试函数设计,聚焦解决高维、多峰、非线性等复杂连续优化问题。算法创新性引入特征交叉策略,在遗传操作中融合父代解的关键维…

2026/10/13 17:33:46 阅读更多 →
CoDeSys数据类型选型与内存优化:从INT溢出到RETAIN策略

CoDeSys数据类型选型与内存优化:从INT溢出到RETAIN策略

写 CoDeSys 的人大概都有过这种经历:程序写得挺顺,逻辑也能跑,但一上真机就出些说不清的鬼事。比如模拟量输入偶尔跳出 65535,或者计数器到了某个数之后突然变成负数,又或者在线监控的时候发现一个看起来不大的结构体变…

2026/10/13 17:33:45 阅读更多 →
WorkBuddy:面向科研复现的终端原生协作操作系统

WorkBuddy:面向科研复现的终端原生协作操作系统

1. WorkBuddy不是“科研版微信”,而是你实验室里少了一块的拼图“科研人快速入门WorkBuddy的方法!(附指令)”——这个标题乍看像极了某知识付费小红书笔记,但实际点开后发现正文空空如也,连一句说明都没有。我第一次看到时也愣了三…

2026/10/13 17:33:45 阅读更多 →
JavaScript学习笔记整理法:从课程到排错手册

JavaScript学习笔记整理法:从课程到排错手册

这套课程我前后刷了三遍。第一遍开着倍速当背景音,第二遍老老实实跟着敲代码,第三遍是准备面试前拿出来重新过了一遍。最后真正让我把 JavaScript 学会的,不是视频里那些听得我连连点头的知识点,而是两样东西:持续地写…

2026/10/13 17:33:45 阅读更多 →
变压器DGA油色谱数据建模避坑指南:从数据体检到随机森林基线

变压器DGA油色谱数据建模避坑指南:从数据体检到随机森林基线

简介:这份变压器溶解气体数据.rar面向电力设备运维、故障诊断研究与机器学习建模人员,聚焦变压器油中溶解气体分析这一典型场景,帮助解决故障类型识别与状态评估问题。数据覆盖中低温过热、高温过热、低能放电、高能放电、局部放电及正常状态…

2026/10/13 17:33:45 阅读更多 →
fiji-win64 安装、宏脚本与插件管理:ImageJ 生物图像分析实战指南

fiji-win64 安装、宏脚本与插件管理:ImageJ 生物图像分析实战指南

简介:Fiji(ImageJ)是一个面向 Windows 六十四位系统的免安装图像处理软件,专门服务于科研和医学图像分析用户。解压后不需要安装即可运行,能够直接读取 Raw 格式原始数据,对八位以上的高动态范围图像支持良…

2026/10/13 17:32:45 阅读更多 →

日新闻

物联网系统集成避坑指南:从选型到验收的关键要点

物联网系统集成避坑指南:从选型到验收的关键要点

做了这么多年物联网系统集成的项目,我最大的感受是,大部分坑不是出在技术前沿,而是出在一些看起来很平常的地方。设备选了便宜的、协议没问清楚、现场没摸透、验收标准写得含糊——这些细枝末节,最后都会变成延期、超支和扯皮的大…

2026/10/13 0:00:57 阅读更多 →
ESP32舵机控制全攻略:从PWM原理到高级玩法与常见故障排查

ESP32舵机控制全攻略:从PWM原理到高级玩法与常见故障排查

如果你玩ESP32有一阵子了,很难不碰到舵机控制这道坎。做智能小车要转方向,做机械臂要抬关节,做云台要偏航俯仰,背后都是同一个东西在干活:舵机。而我见过太多人卡在“代码能编译、舵机就是不听话”这个阶段&#xff0c…

2026/10/13 0:00:57 阅读更多 →
CAN总线原理与STM32实战:抗干扰、仲裁、错误处理全解析

CAN总线原理与STM32实战:抗干扰、仲裁、错误处理全解析

1. 为什么CAN总线至今仍是工业现场的“硬通货”?——从汽车底盘到智能温室的真实需求你有没有拆过一辆老款燃油车的中控台?或者调试过某高校实验室里那台跑着Modbus协议却频频丢帧的PLC控制柜?又或者,在某个农业物联网项目里&…

2026/10/13 0:00:57 阅读更多 →

周新闻

流感时间序列预测实战:ARIMA/LSTM全流程拆解与避坑指南

流感时间序列预测实战:ARIMA/LSTM全流程拆解与避坑指南

简介:基于 ARIMA、LSTM、Transformer 等模型的流感时间序列预测 Python 源码,面向计算机相关专业课程设计与期末大作业学生,以及项目实战学习者。内容覆盖预处理、平稳性检验、定阶、残差分析、多模型对比预测的完整时序建模流程,…

2026/10/13 2:54:56 阅读更多 →
影刀RPA新手教程:键盘模拟输入实战——输入文本与模拟按键的区别

影刀RPA新手教程:键盘模拟输入实战——输入文本与模拟按键的区别

影刀RPA新手教程:键盘模拟输入实战——输入文本与模拟按键的区别 做影刀RPA自动化,十个新手有八个栽在"往输入框里填东西"这件事上:要么填不进去,要么填了一半,要么直接把原来内容追加在后面。这背后的根因&…

2026/10/13 16:22:04 阅读更多 →
影刀RPA新手教程:阅文起点小说数据采集实战——书籍信息与章节内容

影刀RPA新手教程:阅文起点小说数据采集实战——书籍信息与章节内容

影刀RPA新手教程:阅文起点小说数据采集实战——书籍信息与章节内容 1. 认识影刀:什么场景该用RPA采小说数据 起点中文网的页面结构相对稳定——分类榜单、书籍详情、章节内容三块独立页面,跳转链路清晰。这种场景非常适合影刀自动化&#x…

2026/10/13 1:58:08 阅读更多 →

月新闻

我发现了一个新思路:用 Remotion + Claude Code 像写代码一样自动化生成短视频

我发现了一个新思路:用 Remotion + Claude Code 像写代码一样自动化生成短视频

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/12 21:05:09 阅读更多 →
Windows下 Codex 中 Chrome 和 Computer Use 插件不可用问题排查及解决参考方式:TaoToken 统一 Key 配置与验证

Windows下 Codex 中 Chrome 和 Computer Use 插件不可用问题排查及解决参考方式:TaoToken 统一 Key 配置与验证

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/13 14:57:39 阅读更多 →
黑夜航拍船只数据集训练YOLOV5模型全流程解析

黑夜航拍船只数据集训练YOLOV5模型全流程解析

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/12 18:11:17 阅读更多 →