你是否曾经为了处理未知结构的JSON而写了一大堆反射代码?是否为了实现灵活的API而让代码变得臃肿不堪?今天我们来聊聊C#中一个被严重低估的特性——动态编程(Dynamic Programming)。
掌握System.Dynamic命名空间,你将告别繁琐的反射操作,让代码变得更加优雅和高效。本文将通过5个实战场景,带你深入理解并应用C#的动态特性。
在实际开发中,我们经常遇到这些痛点:
ExpandoObject是动态编程的明星类,它允许我们在运行时动态添加和删除成员。这是个好东西,还没有这个出来前处理这个挺麻烦的。
// 使用ExpandoObject的优雅方式publicclassDynamicApproach{publicvoidProcessJson(string json) {dynamic obj = JsonSerializer.Deserialize<ExpandoObject>(json);// 直接访问属性,如同静态类型一样自然 Console.WriteLine($"姓名: {obj.name}"); Console.WriteLine($"年龄: {obj.age}");// 动态添加新属性 obj.processTime = DateTime.Now; obj.status = "已处理"; }}
💰 实际应用场景:
⚠️ 坑点提醒:
当ExpandoObject无法满足复杂需求时,继承DynamicObject可以实现完全自定义的动态行为。
using System.Dynamic;using System.Net.Http.Json;using System.Text.Json;using System.Text.Json.Serialization;namespaceAppDynamicProgramming{internalclassProgram {staticvoidMain(string[] args) {dynamic config = new SmartConfig();// 动态设置属性 config.DatabaseUrl = "localhost:1433"; config.MaxConnections = 100; config.EnableCache = true;// 动态调用方法var updateTime = config.GetUpdateTime("DatabaseUrl");var allProps = config.GetAllProperties(); Console.WriteLine($"数据库连接串更新时间: {updateTime}"); Console.WriteLine($"所有配置项: {string.Join(", ", allProps)}"); } }publicclassSmartConfig : DynamicObject {private Dictionary<string, object> _properties = new Dictionary<string, object>();private Dictionary<string, DateTime> _updateTimes = new Dictionary<string, DateTime>();// 重写获取成员的行为publicoverrideboolTryGetMember(GetMemberBinder binder, outobject result) {string propertyName = binder.Name;// 添加日志记录 Console.WriteLine($"访问属性: {propertyName}");return _properties.TryGetValue(propertyName, out result); }// 重写设置成员的行为publicoverrideboolTrySetMember(SetMemberBinder binder, objectvalue) {string propertyName = binder.Name;// 自动记录更新时间 _updateTimes[propertyName] = DateTime.Now; _properties[propertyName] = value; Console.WriteLine($"设置属性: {propertyName} = {value}");returntrue; }// 自定义方法调用publicoverrideboolTryInvokeMember(InvokeMemberBinder binder, object[] args, outobject result) { result = null;switch (binder.Name) {case"GetUpdateTime":if (args.Length == 1 && args[0] isstring propName) { result = _updateTimes.TryGetValue(propName, out DateTime time) ? time : (DateTime?)null;returntrue; }break;case"GetAllProperties": result = _properties.Keys.ToList();returntrue; }returnfalse; } }}
💰 实际应用场景:
dynamic关键字让我们可以绕过编译时类型检查,实现真正的鸭子类型编程。不过大量用这个dynamic效率肯定会低一些的,这块需要自己把握 。
publicclassDynamicDemo{// 通用的对象处理方法publicvoidProcessAnyObject(dynamic obj) {// 不管obj是什么类型,只要有这些成员就能正常工作try { Console.WriteLine($"Name: {obj.Name}"); Console.WriteLine($"Value: {obj.Value}"); obj.Process(); }catch (RuntimeBinderException ex) { Console.WriteLine($"动态绑定失败: {ex.Message}"); } }// COM组件交互的完美解决方案publicvoidWorkWithExcel() {// 不需要引用Office PIA程序集 Type excelType = Type.GetTypeFromProgID("Excel.Application");dynamic excel = Activator.CreateInstance(excelType); excel.Visible = true;dynamic workbook = excel.Workbooks.Add();dynamic worksheet = workbook.ActiveSheet;// 直接操作Excel对象,如同本地对象一样 worksheet.Cells[1, 1].Value = "Hello Dynamic!"; worksheet.Cells[1, 2].Value = DateTime.Now;// 清理资源 workbook.Close(); excel.Quit(); }}// 测试不同类型的对象publicclassPerson{publicstring Name { get; set; } = "张三";publicint Value { get; set; } = 100;publicvoidProcess() => Console.WriteLine("Person.Process() called");}publicclassProduct{publicstring Name { get; set; } = "iPhone";publicdecimal Value { get; set; } = 9999;publicvoidProcess() => Console.WriteLine("Product.Process() called");}💰 实际应用场景:
对于需要完全控制动态行为的场景,实现IDynamicMetaObjectProvider接口提供了最大的灵活性。
using Microsoft.CSharp.RuntimeBinder;using System;using System.Dynamic;using System.Linq.Expressions;using System.Reflection;namespaceAppDynamicProgramming{publicclassPerson {publicstring Name { get; set; }publicint Value { get; set; } }internalclassProgram {staticvoidMain(string[] args) {var person = new Person { Name = "李四", Value = 200 };dynamic proxy = new FlexibleProxy(person);var flexProxy = (FlexibleProxy)proxy; // 转换为具体类型 flexProxy.AddMethod("GetInfo", args => $"姓名: {person.Name}, 数值: {person.Value}"); flexProxy.AddMethod("Calculate", args => (int)args[0] + person.Value); Console.WriteLine(proxy.GetInfo()); Console.WriteLine($"计算结果: {proxy.Calculate(50)}"); } }publicclassFlexibleProxy : IDynamicMetaObjectProvider {privatereadonlyobject _target;privatereadonly Dictionary<string, Func<object[], object>> _customMethods;publicFlexibleProxy(object target) { _target = target; _customMethods = new Dictionary<string, Func<object[], object>>(); }// 添加自定义方法publicvoidAddMethod(string name, Func<object[], object> method) { _customMethods[name] = method; }public DynamicMetaObject GetMetaObject(Expression parameter) {returnnew FlexibleProxyMetaObject(parameter, this, _target); }internalboolTryInvokeCustomMethod(string name, object[] args, outobject result) {if (_customMethods.TryGetValue(name, outvar method)) { result = method(args);returntrue; } result = null;returnfalse; } }// 自定义MetaObject实现publicclassFlexibleProxyMetaObject : DynamicMetaObject {privatereadonly FlexibleProxy _proxy;privatereadonlyobject _target;publicFlexibleProxyMetaObject(Expression expression, FlexibleProxy proxy, object target) : base(expression, BindingRestrictions.Empty, proxy) { _proxy = proxy; _target = target; }publicoverride DynamicMetaObject BindInvokeMember(InvokeMemberBinder binder, DynamicMetaObject[] args) {var resultVar = Expression.Variable(typeof(object), "result");var argExprs = args.Select(a => Expression.Convert(a.Expression, typeof(object)));var argsArray = Expression.NewArrayInit(typeof(object), argExprs);var tryCustomMethodCall = Expression.Call( Expression.Constant(_proxy),typeof(FlexibleProxy).GetMethod("TryInvokeCustomMethod", BindingFlags.NonPublic | BindingFlags.Instance), Expression.Constant(binder.Name), argsArray, resultVar );var condition = Expression.Condition( tryCustomMethodCall, resultVar, Expression.Throw( Expression.New(typeof(MissingMethodException).GetConstructor(new[] { typeof(string) }), Expression.Constant($"Method '{binder.Name}' not found.") ),typeof(object) ),typeof(object) );var block = Expression.Block(new[] { resultVar }, condition );returnnew DynamicMetaObject(block, BindingRestrictions.GetTypeRestriction(Expression, LimitType)); } }}
publicclassPerformanceOptimizedDynamic{// 缓存CallSite以提高性能privatestaticreadonly ConcurrentDictionary<string, CallSite<Func<CallSite, object, object>>> GetMemberSites = new ConcurrentDictionary<string, CallSite<Func<CallSite, object, object>>>();privatestaticreadonly ConcurrentDictionary<string, CallSite<Func<CallSite, object, object, object>>> SetMemberSites = new ConcurrentDictionary<string, CallSite<Func<CallSite, object, object, object>>>();publicstaticobjectGetMember(object target, string memberName) {var site = GetMemberSites.GetOrAdd(memberName, name => CallSite<Func<CallSite, object, object>>.Create( Binder.GetMember(CSharpBinderFlags.None, name, typeof(PerformanceOptimizedDynamic), new[] { CSharpArgumentInfo.Create(CSharpArgumentInfoFlags.None, null) }) ) );return site.Target(site, target); }publicstaticvoidSetMember(object target, string memberName, objectvalue) {var site = SetMemberSites.GetOrAdd(memberName, name => CallSite<Func<CallSite, object, object, object>>.Create( Binder.SetMember(CSharpBinderFlags.None, name, typeof(PerformanceOptimizedDynamic),new[] { CSharpArgumentInfo.Create(CSharpArgumentInfoFlags.None, null), CSharpArgumentInfo.Create(CSharpArgumentInfoFlags.None, null) }) ) ); site.Target(site, target, value); }// 性能测试对比publicstaticvoidPerformanceTest() {var obj = new ExpandoObject();dynamic dynamicObj = obj;// 预热for (int i = 0; i < 1000; i++) { dynamicObj.TestProperty = i;varvalue = dynamicObj.TestProperty; }// 测试直接dynamic调用var sw1 = Stopwatch.StartNew();for (int i = 0; i < 100000; i++) { dynamicObj.TestProperty = i;varvalue = dynamicObj.TestProperty; } sw1.Stop();// 测试缓存CallSite调用var sw2 = Stopwatch.StartNew();for (int i = 0; i < 100000; i++) { SetMember(obj, "TestProperty", i);varvalue = GetMember(obj, "TestProperty"); } sw2.Stop(); Console.WriteLine($"直接dynamic调用: {sw1.ElapsedMilliseconds}ms"); Console.WriteLine($"缓存CallSite调用: {sw2.ElapsedMilliseconds}ms"); }}按一惯逻辑,写一缓存肯定效率上去,实际上dynamic每个调用点生成CallSite,效率更快。
上面版本需要修改一下,缓存版本才会快一些。
// 获取缓存的CallSite(避免循环中的字典查找)var setSite = SetMemberSites.GetOrAdd("TestProperty", name => CallSite<Func<CallSite, object, object, object>>.Create( Binder.SetMember(CSharpBinderFlags.None, name, typeof(PerformanceOptimizedDynamic),new[] { CSharpArgumentInfo.Create(CSharpArgumentInfoFlags.None, null), CSharpArgumentInfo.Create(CSharpArgumentInfoFlags.None, null) }) ));var getSite = GetMemberSites.GetOrAdd("TestProperty", name => CallSite<Func<CallSite, object, object>>.Create( Binder.GetMember(CSharpBinderFlags.None, name, typeof(PerformanceOptimizedDynamic),new[] { CSharpArgumentInfo.Create(CSharpArgumentInfoFlags.None, null) }) ));// 测试缓存CallSite调用var sw2 = Stopwatch.StartNew();for (int i = 0; i < 10000000; i++){ setSite.Target(setSite, obj, i);varvalue = getSite.Target(getSite, obj);}sw2.Stop();
// 错误示例 - 能用静态类型就别用dynamicpublicdynamicProcessData(dynamic input){returnnew { Result = input.Value * 2 };}// 正确示例 - 明确输入输出类型public ProcessResult ProcessData<T>(T input) where T : IProcessable{returnnew ProcessResult { Value = input.Process() };}在属性名固定的情况下,直接使用
dynamic是最优选择;在属性名动态变化时,缓存CallSite才能体现出优势。
// 错误示例 - 没有异常处理publicvoidBadExample(dynamic obj){var result = obj.SomeMethod(); // 可能抛出RuntimeBinderException}// 正确示例 - 完善的异常处理publicvoidGoodExample(dynamic obj){try {if (HasMethod(obj, "SomeMethod")) {var result = obj.SomeMethod(); } }catch (RuntimeBinderException ex) {// 记录日志并提供降级方案 Logger.LogWarning($"动态调用失败: {ex.Message}"); }}privateboolHasMethod(object obj, string methodName){return obj.GetType().GetMethod(methodName) != null;}publicstaticclassJsonDynamicHelper{publicstaticdynamicParseAndEnhance(string json) {dynamic obj = JsonConvert.DeserializeObject<ExpandoObject>(json);var dict = (IDictionary<string, object>)obj;// 添加元数据 dict["_parseTime"] = DateTime.Now; dict["_hasError"] = false;return obj; }}publicstaticclassSafeDynamic{publicstatic T GetValue<T>(dynamic obj, string propertyName, T defaultValue = default(T)) {try {var dict = obj as IDictionary<string, object>;if (dict?.ContainsKey(propertyName) == true) {return (T)Convert.ChangeType(dict[propertyName], typeof(T)); }return defaultValue; }catch {return defaultValue; } }}publicclassDynamicBuilder{privatereadonly ExpandoObject _obj = new ExpandoObject();public DynamicBuilder Set(string key, objectvalue) { ((IDictionary<string, object>)_obj)[key] = value;returnthis; }publicdynamicBuild() => _obj;}通过本文的学习,我们掌握了C#动态编程的精髓:
动态编程不是银弹,但在正确的场景下使用,能让我们的代码更加优雅和灵活。记住:能用静态类型就用静态类型,需要灵活性时才考虑动态特性。
🤔 思考题:
💬 互动时间:
如果你在使用C#动态特性时遇到过什么有趣的问题或有独特的应用场景,欢迎在评论区分享!让我们一起探讨更多动态编程的可能性。
觉得这篇文章对你有帮助吗?请转发给更多需要的C#开发同行,让更多人受益于动态编程的强大威力!
关注我,获取更多C#开发实战技巧和最佳实践分享!