C# dynamic 关键字 使用详解

总目录


前言

dynamic 是 C# 4.0 引入的关键字,用于声明动态类型,允许在运行时解析类型和成员,而非编译时。它主要设计用于简化与动态语言(如 Python、JavaScript)的交互、处理未知结构的数据(如 JSON、XML)以及减少反射代码的复杂性。


一、基本概念

  • 动态类型解析:编译器不会对 dynamic 变量进行类型检查,所有操作(方法调用、属性访问)在运行时解析。
  • 底层机制:由 DLR(Dynamic Language Runtime)驱动,使用 IDynamicMetaObjectProvider 接口
  • 适用场景
    • 与动态语言(IronPython、JavaScript)交互。
    • 处理未知结构的动态数据(如反序列化 JSON)。
    • 简化反射代码。
    • COM 互操作(如操作 Excel 对象模型)。
  • 性能代价:动态类型解析比静态类型慢,需谨慎使用高频代码。

二、基本用法

1. 动态类型申明

dynamic value = "Hello World";
Console.WriteLine(value.GetType()); // 输出: System.String

value = 42;                        // 动态变量可重新赋值为任意类型
Console.WriteLine(value.GetType()); // 输出: System.Int32

value = "Hello";                   // 运行时切换为 string
Console.WriteLine(value.Length);  // 输出 5

value = new List<int> { 1, 2, 3 };
value.Add(4);              		  // 运行时调用 Add 方法

dynamic person = new { Name = "John", Age = 30 };
Console.WriteLine(person.Name); // 输出: John

2. 调用未知方法或属性

dynamic obj = new ExpandoObject();
obj.Name = "Alice";                // 动态添加属性
obj.Print = new Action(() => Console.WriteLine(obj.Name));

obj.Print();                       // 输出: Alice

3. 动态方法调用

public class Calculator
{
    public int Add(int a, int b) => a + b;
}

class Program
{
    static void Main()
    {
        dynamic calc = new Calculator();
        int result = calc.Add(3, 4);       // 编译时不检查方法是否存在
        Console.WriteLine(result);         // 输出: 7
    }
}

三、dynamic vs object vs var

特性 dynamic object var
类型检查时机 运行时 编译时 编译时(类型推断)
成员访问 动态解析 需要强制转换 静态类型访问
性能 较慢 需要拆箱 最优
使用场景 动态交互 通用对象容器 类型声明简化
智能提示支持
代码灵活性 高(适应未知结构) 低(需明确类型) 适中

四、应用场景

1. 动态类型与反射

动态类型可简化反射操作,但需权衡性能与可读性:

反射实现

object obj = Activator.CreateInstance(typeof(MyClass));
MethodInfo method = typeof(MyClass).GetMethod("MyMethod");
method.Invoke(obj, new object[] { 42 });

动态类型实现

dynamic obj = Activator.CreateInstance(typeof(MyClass));
obj.MyMethod(42);  // 代码更简洁
// 传统反射方式
object obj = Activator.CreateInstance(typeof(MyClass));
MethodInfo method = obj.GetType().GetMethod("DoWork");
method.Invoke(obj, null);

// 使用 dynamic 简化
dynamic dynObj = Activator.CreateInstance(typeof(MyClass));
dynObj.DoWork();  // 直接调用方法

2. 动态对象(ExpandoObjectDynamicObject

1. ExpandoObject

允许动态添加属性和方法:

    static void Main()
    {
        dynamic person = new ExpandoObject();
        person.Name = "Bob";
        person.Age = 25;
        person.SayHello = (Action)(() => Console.WriteLine($"Hi, I'm {person.Name}"));

        person.SayHello();  // 输出 "Hi, I'm Bob"

        // 转换为字典
        var dict = (IDictionary<string, object>)person;
        
        Console.WriteLine(dict["Name"]);  // 输出 Bob
        Console.WriteLine(dict["Age"]);  // 输出 25
        Action action= (Action)dict["SayHello"];
        action?.Invoke();               //输出 "Hi, I'm Bob"
    }

2. 自定义 DynamicObject

实现动态行为控制:

public class DynamicDictionary : DynamicObject
{
    private Dictionary<string, object> _dict = new Dictionary<string, object>();

    public override bool TryGetMember(GetMemberBinder binder, out object result)
    {
        return _dict.TryGetValue(binder.Name, out result);
    }

    public override bool TrySetMember(SetMemberBinder binder, object value)
    {
        _dict[binder.Name] = value;
        return true;
    }
}

class Program
{
    static void Main()
    {
        dynamic dict = new DynamicDictionary();
        dict.City = "Shanghai";
        Console.WriteLine(dict.City); // 输出: Shanghai
    }
}

3. 处理动态数据(JSON 示例)

使用 Newtonsoft.Json(或 System.Text.Json)处理动态 JSON:

    static void Main()
    {
        string json= """{ "Name": "Alice", "Age": 30 }""";
        
        dynamic data = JObject.Parse(json); 
        //或使用下面的方式
        //dynamic data = JsonConvert.DeserializeObject(json);

        Console.WriteLine(data.Name);  // 输出 Alice
        Console.WriteLine(data.Age + 5); // 输出 35

        // 动态扩展属性
        data.Country = "USA";  // 运行时添加新属性
        Console.WriteLine(data.Country);// 输出 USA

    }

4. COM 互操作(Excel 自动化)

Type excelType = Type.GetTypeFromProgID("Excel.Application");
dynamic excel = Activator.CreateInstance(excelType);

excel.Visible = true;          // 设置属性
dynamic workbook = excel.Workbooks.Add();
dynamic worksheet = workbook.ActiveSheet;

worksheet.Cells[1, 1] = "Hello World";  // 动态访问单元格

五、注意事项与最佳实践

1. 注意事项

  • 避免过度使用
    • 优先使用静态类型确保安全性和性能。
    • 仅在必要时(如处理动态数据、COM 互操作)使用 dynamic
  • 无智能提示
    • 成员解析完全在运行时完成
  • 错误处理
    • 动态调用可能抛出 RuntimeBinderException,需捕获异常。
    • 错误延迟:类型错误在运行时才会暴露
    try
    {
        dynamic obj = new object();
        obj.InvalidMethod();
    }
    catch (RuntimeBinderException ex)
    {
        Console.WriteLine($"运行时错误: {ex.Message}");
    }
    

2. 最佳实践

  • 性能优化
    • 缓存频繁使用的动态操作结果。
    • 在循环或高频代码中避免使用 dynamic
// 缓存高频操作(减少动态解析次数)
dynamic obj = GetDynamicObject();
var cachedAction = (Action)obj.DoWork;  // 转换为委托
for(int i=0; i<1000; i++) {
    cachedAction();  // 比直接调用 obj.DoWork() 更快
}
  • 严格限制使用范围:仅在必需时使用
    • 泛型与接口:通过设计模式避免动态类型。
  • 配合 try-catch:处理可能的运行时异常
  • 单元测试覆盖:针对动态代码增加测试用例
  • 优先选择替代方案
    • 对于已知结构:使用强类型类
    • 对于 JSON:推荐 System.Text.Json 的强类型反序列化
    • 对于反射:考虑 generic 或 delegate 优化

结语

回到目录页:C#/.NET 知识汇总
希望以上内容可以帮助到大家,如文中有不对之处,还请批评指正。

你可能感兴趣的:(C#,c#,windows,开发语言)