你是否曾经为了处理未知结构的JSON而写了一大堆反射代码?是否为了实现灵活的API而让代码变得臃肿不堪?今天我们来聊聊C#中一个被严重低估的特性——动态编程(Dynamic Programming)。
掌握System.Dynamic命名空间,你将告别繁琐的反射操作,让代码变得更加优雅和高效。本文将通过5个实战场景,带你深入理解并应用C#的动态特性。
在实际开发中,我们经常遇到这些痛点:
ExpandoObject是动态编程的明星类,它允许我们在运行时动态添加和删除成员。这是个好东西,还没有这个出来前处理这个挺麻烦的。
c#// 使用ExpandoObject的优雅方式
public class DynamicApproach
{
public void ProcessJson(string json)
{
dynamic obj = JsonSerializer.Deserialize<ExpandoObject>(json);
// 直接访问属性,如同静态类型一样自然
Console.WriteLine($"姓名: {obj.name}");
Console.WriteLine($"年龄: {obj.age}");
// 动态添加新属性
obj.processTime = DateTime.Now;
obj.status = "已处理";
}
}

💰 实际应用场景:
⚠️ 坑点提醒:
当ExpandoObject无法满足复杂需求时,继承DynamicObject可以实现完全自定义的动态行为。
c#using System.Dynamic;
using System.Net.Http.Json;
using System.Text.Json;
using System.Text.Json.Serialization;
namespace AppDynamicProgramming
{
internal class Program
{
static void Main(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)}");
}
}
public class SmartConfig : DynamicObject
{
private Dictionary<string, object> _properties = new Dictionary<string, object>();
private Dictionary<string, DateTime> _updateTimes = new Dictionary<string, DateTime>();
// 重写获取成员的行为
public override bool TryGetMember(GetMemberBinder binder, out object result)
{
string propertyName = binder.Name;
// 添加日志记录
Console.WriteLine($"访问属性: {propertyName}");
return _properties.TryGetValue(propertyName, out result);
}
// 重写设置成员的行为
public override bool TrySetMember(SetMemberBinder binder, object value)
{
string propertyName = binder.Name;
// 自动记录更新时间
_updateTimes[propertyName] = DateTime.Now;
_properties[propertyName] = value;
Console.WriteLine($"设置属性: {propertyName} = {value}");
return true;
}
// 自定义方法调用
public override bool TryInvokeMember(InvokeMemberBinder binder, object[] args, out object result)
{
result = null;
switch (binder.Name)
{
case "GetUpdateTime":
if (args.Length == 1 && args[0] is string propName)
{
result = _updateTimes.TryGetValue(propName, out DateTime time) ? time : (DateTime?)null;
return true;
}
break;
case "GetAllProperties":
result = _properties.Keys.ToList();
return true;
}
return false;
}
}
}

💰 实际应用场景:
dynamic关键字让我们可以绕过编译时类型检查,实现真正的鸭子类型编程。不过大量用这个dynamic效率肯定会低一些的,这块需要自己把握 。
c#public class DynamicDemo
{
// 通用的对象处理方法
public void ProcessAnyObject(dynamic obj)
{
// 不管obj是什么类型,只要有这些成员就能正常工作
try
{
Console.WriteLine($"Name: {obj.Name}");
Console.WriteLine($"Value: {obj.Value}");
obj.Process();
}
catch (RuntimeBinderException ex)
{
Console.WriteLine($"动态绑定失败: {ex.Message}");
}
}
// COM组件交互的完美解决方案
public void WorkWithExcel()
{
// 不需要引用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();
}
}
// 测试不同类型的对象
public class Person
{
public string Name { get; set; } = "张三";
public int Value { get; set; } = 100;
public void Process() => Console.WriteLine("Person.Process() called");
}
public class Product
{
public string Name { get; set; } = "iPhone";
public decimal Value { get; set; } = 9999;
public void Process() => Console.WriteLine("Product.Process() called");
}
💰 实际应用场景:
对于需要完全控制动态行为的场景,实现IDynamicMetaObjectProvider接口提供了最大的灵活性。
c#using Microsoft.CSharp.RuntimeBinder;
using System;
using System.Dynamic;
using System.Linq.Expressions;
using System.Reflection;
namespace AppDynamicProgramming
{
public class Person
{
public string Name { get; set; }
public int Value { get; set; }
}
internal class Program
{
static void Main(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)}");
}
}
public class FlexibleProxy : IDynamicMetaObjectProvider
{
private readonly object _target;
private readonly Dictionary<string, Func<object[], object>> _customMethods;
public FlexibleProxy(object target)
{
_target = target;
_customMethods = new Dictionary<string, Func<object[], object>>();
}
// 添加自定义方法
public void AddMethod(string name, Func<object[], object> method)
{
_customMethods[name] = method;
}
public DynamicMetaObject GetMetaObject(Expression parameter)
{
return new FlexibleProxyMetaObject(parameter, this, _target);
}
internal bool TryInvokeCustomMethod(string name, object[] args, out object result)
{
if (_customMethods.TryGetValue(name, out var method))
{
result = method(args);
return true;
}
result = null;
return false;
}
}
// 自定义MetaObject实现
public class FlexibleProxyMetaObject : DynamicMetaObject
{
private readonly FlexibleProxy _proxy;
private readonly object _target;
public FlexibleProxyMetaObject(Expression expression, FlexibleProxy proxy, object target)
: base(expression, BindingRestrictions.Empty, proxy)
{
_proxy = proxy;
_target = target;
}
public override 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
);
return new DynamicMetaObject(block, BindingRestrictions.GetTypeRestriction(Expression, LimitType));
}
}
}

c#public class PerformanceOptimizedDynamic
{
// 缓存CallSite以提高性能
private static readonly ConcurrentDictionary<string, CallSite<Func<CallSite, object, object>>>
GetMemberSites = new ConcurrentDictionary<string, CallSite<Func<CallSite, object, object>>>();
private static readonly ConcurrentDictionary<string, CallSite<Func<CallSite, object, object, object>>>
SetMemberSites = new ConcurrentDictionary<string, CallSite<Func<CallSite, object, object, object>>>();
public static object GetMember(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);
}
public static void SetMember(object target, string memberName, object value)
{
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);
}
// 性能测试对比
public static void PerformanceTest()
{
var obj = new ExpandoObject();
dynamic dynamicObj = obj;
// 预热
for (int i = 0; i < 1000; i++)
{
dynamicObj.TestProperty = i;
var value = dynamicObj.TestProperty;
}
// 测试直接dynamic调用
var sw1 = Stopwatch.StartNew();
for (int i = 0; i < 100000; i++)
{
dynamicObj.TestProperty = i;
var value = dynamicObj.TestProperty;
}
sw1.Stop();
// 测试缓存CallSite调用
var sw2 = Stopwatch.StartNew();
for (int i = 0; i < 100000; i++)
{
SetMember(obj, "TestProperty", i);
var value = GetMember(obj, "TestProperty");
}
sw2.Stop();
Console.WriteLine($"直接dynamic调用: {sw1.ElapsedMilliseconds}ms");
Console.WriteLine($"缓存CallSite调用: {sw2.ElapsedMilliseconds}ms");
}
}
按一惯逻辑,写一缓存肯定效率上去,实际上dynamic每个调用点生成CallSite,效率更快。
上面版本需要修改一下,缓存版本才会快一些。
c#// 获取缓存的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);
var value = getSite.Target(getSite, obj);
}
sw2.Stop();

c#// 错误示例 - 能用静态类型就别用dynamic
public dynamic ProcessData(dynamic input)
{
return new { Result = input.Value * 2 };
}
// 正确示例 - 明确输入输出类型
public ProcessResult ProcessData<T>(T input) where T : IProcessable
{
return new ProcessResult { Value = input.Process() };
}
在属性名固定的情况下,直接使用
dynamic是最优选择;在属性名动态变化时,缓存CallSite才能体现出优势。
c#// 错误示例 - 没有异常处理
public void BadExample(dynamic obj)
{
var result = obj.SomeMethod(); // 可能抛出RuntimeBinderException
}
// 正确示例 - 完善的异常处理
public void GoodExample(dynamic obj)
{
try
{
if (HasMethod(obj, "SomeMethod"))
{
var result = obj.SomeMethod();
}
}
catch (RuntimeBinderException ex)
{
// 记录日志并提供降级方案
Logger.LogWarning($"动态调用失败: {ex.Message}");
}
}
private bool HasMethod(object obj, string methodName)
{
return obj.GetType().GetMethod(methodName) != null;
}
c#public static class JsonDynamicHelper
{
public static dynamic ParseAndEnhance(string json)
{
dynamic obj = JsonConvert.DeserializeObject<ExpandoObject>(json);
var dict = (IDictionary<string, object>)obj;
// 添加元数据
dict["_parseTime"] = DateTime.Now;
dict["_hasError"] = false;
return obj;
}
}
c#public static class SafeDynamic
{
public static 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;
}
}
}
c#public class DynamicBuilder
{
private readonly ExpandoObject _obj = new ExpandoObject();
public DynamicBuilder Set(string key, object value)
{
((IDictionary<string, object>)_obj)[key] = value;
return this;
}
public dynamic Build() => _obj;
}
通过本文的学习,我们掌握了C#动态编程的精髓:
动态编程不是银弹,但在正确的场景下使用,能让我们的代码更加优雅和灵活。记住:能用静态类型就用静态类型,需要灵活性时才考虑动态特性。
🤔 思考题:
💬 互动时间:
如果你在使用C#动态特性时遇到过什么有趣的问题或有独特的应用场景,欢迎在评论区分享!让我们一起探讨更多动态编程的可能性。
觉得这篇文章对你有帮助吗?请转发给更多需要的C#开发同行,让更多人受益于动态编程的强大威力!
关注我,获取更多C#开发实战技巧和最佳实践分享!
本文作者:技术老小子
本文链接:
版权声明:本博客所有文章除特别声明外,均采用 BY-NC-SA 许可协议。转载请注明出处!