C#的dynamic类型允许我们在编译阶段跳过类型检查,将类型解析的工作推迟到运行时完成,这为动态实现方法和属性的调用提供了便利。我们可以通过自定义动态对象,灵活扩展方法和属性的动态操作能力,适配多变的业务场景。

dynamic类型基础特性
dynamic类型是C# 4.0引入的静态类型,它在编译时会被标记为dynamic,编译器不会对其成员访问做静态类型检查,所有类型检查会在运行时由DLR(动态语言运行时)完成。它和object类型的最大区别在于,object类型的成员调用需要显式类型转换,而dynamic类型可以直接调用成员,不需要提前知道对象的具体类型。
下面是一段基础的dynamic类型使用示例:
using System;
class Program
{
static void Main()
{
// 定义dynamic类型变量
dynamic dynamicValue = 10;
Console.WriteLine(dynamicValue); // 输出10
// 动态修改变量类型,不需要显式转换
dynamicValue = "hello dynamic";
Console.WriteLine(dynamicValue.Length); // 输出13,运行时解析Length属性
}
}自定义动态对象实现动态属性
要实现自定义的方法和属性动态调用,需要继承DynamicObject类,并重写对应的虚方法。首先我们来看动态属性的实现,需要重写TryGetMember和TrySetMember方法,这两个方法分别处理属性的读取和赋值操作。
下面是一个支持动态属性的自定义动态对象实现:
using System;
using System.Collections.Generic;
using System.Dynamic;
public class DynamicPropertyObject : DynamicObject
{
// 存储动态属性的字典
private Dictionary<string, object> _properties = new Dictionary<string, object>();
// 重写属性读取方法
public override bool TryGetMember(GetMemberBinder binder, out object result)
{
// 检查属性是否存在
if (_properties.ContainsKey(binder.Name))
{
result = _properties[binder.Name];
return true;
}
// 属性不存在时返回null
result = null;
return true;
}
// 重写属性赋值方法
public override bool TrySetMember(SetMemberBinder binder, object value)
{
// 存储属性值
_properties[binder.Name] = value;
return true;
}
}使用这个动态对象时,我们可以随意添加和访问属性,不需要提前定义:
using System;
class Program
{
static void Main()
{
dynamic obj = new DynamicPropertyObject();
// 动态设置属性,不需要提前定义Name和Age属性
obj.Name = "张三";
obj.Age = 25;
obj.IsStudent = true;
// 动态读取属性
Console.WriteLine($"姓名:{obj.Name}"); // 输出 姓名:张三
Console.WriteLine($"年龄:{obj.Age}"); // 输出 年龄:25
Console.WriteLine($"是否学生:{obj.IsStudent}"); // 输出 是否学生:True
}
}自定义动态对象实现动态方法
实现动态方法需要重写TryInvokeMember方法,这个方法会在调用动态对象的方法时触发,我们可以在这个方法内部自定义方法的处理逻辑,比如根据方法名动态执行不同的操作。
下面是支持动态方法的自定义动态对象实现:
using System;
using System.Dynamic;
using System.Reflection;
public class DynamicMethodObject : DynamicObject
{
// 重写方法调用逻辑
public override bool TryInvokeMember(InvokeMemberBinder binder, object[] args, out object result)
{
string methodName = binder.Name;
// 根据方法名执行不同逻辑
switch (methodName)
{
case "Add":
// 处理Add方法,假设参数是两个数字
if (args.Length == 2 && args[0] is int a && args[1] is int b)
{
result = a + b;
return true;
}
break;
case "Concat":
// 处理Concat方法,假设参数是两个字符串
if (args.Length == 2 && args[0] is string s1 && args[1] is string s2)
{
result = s1 + s2;
return true;
}
break;
default:
// 未知方法抛出异常
throw new MissingMethodException($"不支持的方法:{methodName}");
}
result = null;
return false;
}
}使用这个动态对象调用动态方法:
using System;
class Program
{
static void Main()
{
dynamic obj = new DynamicMethodObject();
// 调用动态Add方法
int sum = obj.Add(3, 5);
Console.WriteLine($"3+5的结果:{sum}"); // 输出 3+5的结果:8
// 调用动态Concat方法
string concatResult = obj.Concat("Hello ", "World");
Console.WriteLine($"拼接结果:{concatResult}"); // 输出 拼接结果:Hello World
// 调用不存在的方法会抛出异常
try
{
obj.Multiply(2, 3);
}
catch (MissingMethodException ex)
{
Console.WriteLine(ex.Message); // 输出 不支持的方法:Multiply
}
}
}同时支持动态属性和方法的动态对象
我们可以将前面的属性处理逻辑和方法处理逻辑结合,实现一个同时支持动态属性和动态方法的完整动态对象:
using System;
using System.Collections.Generic;
using System.Dynamic;
public class FullDynamicObject : DynamicObject
{
private Dictionary<string, object> _properties = new Dictionary<string, object>();
// 动态属性读取
public override bool TryGetMember(GetMemberBinder binder, out object result)
{
if (_properties.ContainsKey(binder.Name))
{
result = _properties[binder.Name];
return true;
}
result = null;
return true;
}
// 动态属性赋值
public override bool TrySetMember(SetMemberBinder binder, object value)
{
_properties[binder.Name] = value;
return true;
}
// 动态方法调用
public override bool TryInvokeMember(InvokeMemberBinder binder, object[] args, out object result)
{
string methodName = binder.Name;
if (methodName == "PrintAllProperties")
{
// 打印所有动态属性
foreach (var kv in _properties)
{
Console.WriteLine($"{kv.Key}: {kv.Value}");
}
result = null;
return true;
}
result = null;
return false;
}
}使用示例:
using System;
class Program
{
static void Main()
{
dynamic obj = new FullDynamicObject();
// 设置动态属性
obj.Id = 1001;
obj.UserName = "李四";
obj.Score = 95.5;
// 调用动态方法
obj.PrintAllProperties();
// 输出:
// Id: 1001
// UserName: 李四
// Score: 95.5
}
}dynamic动态对象使用注意事项
- dynamic类型的调用是在运行时解析的,如果调用的成员不存在,会在运行时抛出
RuntimeBinderException,所以需要确保调用的成员在运行时是真实存在的。 - dynamic类型的性能比静态类型稍差,因为运行时需要额外的类型解析开销,不适合在高频调用的核心逻辑中大量使用。
- dynamic类型无法享受编译器的智能提示和类型检查,代码编写阶段不容易发现拼写错误等问题,需要额外做好测试。
- dynamic类型不能用于匿名方法、Lambda表达式的参数类型声明,也不能作为扩展方法的第一个参数类型。