当前位置:首页>java>告别反射地狱!C#动态编程让你的代码优雅起飞

告别反射地狱!C#动态编程让你的代码优雅起飞

  • 2026-09-23 11:26:12
告别反射地狱!C#动态编程让你的代码优雅起飞

你是否曾经为了处理未知结构的JSON而写了一大堆反射代码?是否为了实现灵活的API而让代码变得臃肿不堪?今天我们来聊聊C#中一个被严重低估的特性——动态编程(Dynamic Programming)。

掌握System.Dynamic命名空间,你将告别繁琐的反射操作,让代码变得更加优雅和高效。本文将通过5个实战场景,带你深入理解并应用C#的动态特性。

🎯 为什么需要动态编程?

在实际开发中,我们经常遇到这些痛点:

  • • JSON数据结构不固定:接口返回的数据格式经常变化
  • • 配置文件灵活性不够:需要支持动态添加属性
  • • 插件系统扩展困难:第三方组件接口不统一
  • • 反射性能问题:大量使用反射导致性能下降

💡 解决方案:System.Dynamic全家桶

🚀 方案一:ExpandoObject - 动态对象的最佳选择

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 = "已处理";    }}

💰 实际应用场景:

  • • API响应处理
  • • 配置文件读取
  • • 临时数据容器

⚠️ 坑点提醒:

  • • 没有编译时类型检查,拼写错误只能在运行时发现
  • • 性能略低于静态类型

🔧 方案二:DynamicObject - 自定义动态行为

当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;        }    }}

💰 实际应用场景:

  • • 智能配置系统
  • • ORM框架
  • • 插件接口适配器

🌟 方案三:dynamic关键字 - 延迟绑定的魅力

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");}

💰 实际应用场景:

  • • COM组件交互,当年调用excel,word这块还是挺费事的,每个要找接口,用dynamic确定简单不少,不过这些年基本没用过什么com组件了。
  • • 第三方库封装
  • • 动态代理实现

🎨 方案四:IDynamicMetaObjectProvider - 高级定制接口

对于需要完全控制动态行为的场景,实现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();

🔍 常见坑点与解决方案

❌ 常犯错误1:过度使用dynamic

// 错误示例 - 能用静态类型就别用dynamicpublicdynamicProcessData(dynamic input){returnnew { Result = input.Value * 2 };}// 正确示例 - 明确输入输出类型public ProcessResult ProcessData<T>(T input) where T : IProcessable{returnnew ProcessResult { Value = input.Process() };}

在属性名固定的情况下,直接使用dynamic是最优选择;在属性名动态变化时,缓存CallSite才能体现出优势。

❌ 常犯错误2:忽略异常处理

// 错误示例 - 没有异常处理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;}

💎 三个收藏级代码模板

1. 通用JSON动态处理器

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;    }}

2. 安全的动态属性访问器

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;        }    }}

3. 动态对象构建器

publicclassDynamicBuilder{privatereadonly ExpandoObject _obj = new ExpandoObject();public DynamicBuilder Set(string key, objectvalue)    {        ((IDictionary<string, object>)_obj)[key] = value;returnthis;    }publicdynamicBuild() => _obj;}

🎯 总结与展望

通过本文的学习,我们掌握了C#动态编程的精髓:

  1. 1. ExpandoObject:最简单实用的动态对象解决方案
  2. 2. DynamicObject:自定义动态行为的强大基类
  3. 3. dynamic关键字:实现鸭子类型编程的利器

动态编程不是银弹,但在正确的场景下使用,能让我们的代码更加优雅和灵活。记住:能用静态类型就用静态类型,需要灵活性时才考虑动态特性。


🤔 思考题:

  1. 1. 在你的项目中,有哪些场景可以用动态编程来简化代码?
  2. 2. 你觉得动态编程的最大优势和劣势分别是什么?

💬 互动时间:

如果你在使用C#动态特性时遇到过什么有趣的问题或有独特的应用场景,欢迎在评论区分享!让我们一起探讨更多动态编程的可能性。

觉得这篇文章对你有帮助吗?请转发给更多需要的C#开发同行,让更多人受益于动态编程的强大威力!


关注我,获取更多C#开发实战技巧和最佳实践分享!

最新文章

随机文章