-
匿名类型与动态对象——C#测试临时数据封装实例
第一部分:C#基础入门
实例8:匿名类型与动态对象——临时数据封装
实例介绍
在日常开发中,我们经常需要临时存储一些不需要持久化的数据:比如数据库查询的中间结果、前端传递的临时参数、接口返回的非结构化数据。此时,创建专门的实体类会显得冗余,而匿名类型和动态对象就是最佳解决方案:
匿名类型:适合强类型的临时数据(结构固定、属性明确),比如封装用户的“姓名+年龄+邮箱”临时信息;
动态对象:适合不确定类型的临时数据(结构可变、属性未知),比如处理前端传来的JSON、COM组件交互、动态脚本调用。
本实例通过两个场景带你掌握匿名类型和动态对象的核心用法:用匿名类vb.net教程C#教程python教程SQL教程access 2010教程型封装查询结果,用动态对象处理JSON数据,对比两者的区别与适用场景,学会在合适的场景选择合适的工具。
需求分析
1.匿名类型场景:从数据库查询用户信息时,仅需临时返回“姓名、年龄、注册时间”三个字段,无需创建实体类;
2.动态对象场景:处理前端传来的JSON数据(结构不确定,可能包含“手机号、地址”等可选字段),无需提前定义类;
3.核心验证:
匿名类型的只读特性(属性无法修改);
动态对象的运行时属性绑定(可动态添加/删除属性);
两者的类型安全性对比(编译时vs运行时检查);
4.测试验证:创建匿名对象、访问属性,动态对象的属性操作,错误处理(如动态对象访问不存在的属性)。
代码实现
csharp
using System;
using System.Dynamic; // 动态对象依赖的命名空间
using Newtonsoft.Json; // 需安装Newtonsoft.Json包(处理JSON动态转换)
namespace AnonymousAndDynamicDemo
{
class Program
{
static void Main(string[] args)
{
// ==================== 场景1:匿名类型封装临时查询结果 ====================
Console.WriteLine("=== 匿名类型示例 ===");
// 创建匿名对象:用var推断类型,属性自动为只读
var tempUser = new
{
Name = "张三",
Age = 32,
RegisterTime = DateTime.Now.AddYears(-5) // 5年前注册
};
// 访问匿名对象属性(编译时检查,安全)
Console.WriteLine($"姓名:{tempUser.Name}");
Console.WriteLine($"年龄:{tempUser.Age}");
Console.WriteLine($"注册时间:{tempUser.RegisterTime:yyyy-MM-dd}");
// tempUser.Name = "李四"; // ❌ 编译错误:匿名类型属性是只读的
// 匿名类型的相等性:基于属性值的相等(编译器重写了Equals和GetHashCode)
var tempUser2 = new { Name = "张三", Age = 32, RegisterTime = tempUser.RegisterTime };
Console.WriteLine($"匿名对象相等?{tempUser.Equals(tempUser2)}"); // ✅ True
// ==================== 场景2:动态对象处理不确定JSON数据 ====================
Console.WriteLine("
=== 动态对象示例 ===");
// 1. 用ExpandoObject创建动态对象(支持动态添加属性)
dynamic dynamicUser = new ExpandoObject();
dynamicUser.Name = "李四";
dynamicUser.Age = 28;
dynamicUser.Phone = "13812345678"; // 动态添加可选属性
Console.WriteLine($"动态对象姓名:{dynamicUser.Name}");
Console.WriteLine($"动态对象手机号:{dynamicUser.Phone}");
// 2. 动态对象处理JSON数据(前端传来的非结构化数据)
string jsonData = @"
{
""Name"": ""王五"",
""Age"": 25,
""Address"": ""北京市朝阳区"",
""Tags"": [""技术"", ""产品""]
}";
dynamic jsonUser = JsonConvert.DeserializeObject<dynamic>(jsonData);
Console.WriteLine($"JSON用户姓名:{jsonUser.Name}");
Console.WriteLine($"JSON用户地址:{jsonUser.Address}");
Console.WriteLine($"JSON用户标签:{jsonUser.Tags[0]}"); // 访问数组元素
// 3. 动态对象的错误处理(访问不存在的属性会抛出运行时异常)
try
{
Console.WriteLine(jsonUser.NonExistProperty); // ❌ 运行时错误
}
catch (Microsoft.CSharp.RuntimeBinder.RuntimeBinderException ex)
{
Console.WriteLine($"动态对象错误:{ex.Message}");
}
}
}
}
逐行讲解
场景1:匿名类型核心代码
匿名对象创建:
csharp
var tempUser = new { Name = "张三", Age = 32, RegisterTime = DateTime.Now.AddYears(-5) };
ovar关键字:匿名类型没有类名,必须用var让编译器自动推断类型;
o编译器行为:自动生成密封类(类名如<>f__AnonymousType03),属性为public readonly(只读),重写Equals/GetHashCode(基于属性值相等)。
匿名类型只读特性:
csharp
// tempUser.Name = "李四"; // ❌ 编译错误
匿名类型的属性是只读的,无法修改,确保临时数据的不可变性。
匿名类型相等性:
csharp
var tempUser2 = new { Name = "张三", Age = 32, RegisterTime = tempUser.RegisterTime };
Console.WriteLine(tempUser.Equals(tempUser2)); // ✅ True
编译器重写了Equals方法,只要所有属性值相等,两个匿名对象就相等(即使是不同实例)。
场景2:动态对象核心代码
ExpandoObject创建动态对象:
csharp
dynamic dynamicUser = new ExpandoObject();
dynamicUser.Name = "李四";
dynamicUser.Phone = "13812345678";
ExpandoObject:实现IDictionary<string, object>,可以像字典一样添加属性(dynamicUser["Phone"] = "138..."),也可以像对象一样访问(dynamicUser.Phone);
dynamic关键字:告诉编译器不做静态类型检查,所有属性/方法绑定延迟到运行时。
动态对象处理JSON:
csharp
dynamic jsonUser = JsonConvert.DeserializeObject<dynamic>(jsonData);
Console.WriteLine(jsonUser.Address);
Newtonsoft.Json将JSON反序列化为JObject(实现动态绑定),可以直接访问JSON的属性,无需提前定义类;
适合处理结构不确定的JSON(比如前端传递的可选字段)。
动态对象错误处理:
csharp
try { Console.WriteLine(jsonUser.NonExistProperty); }
catch (RuntimeBinderException ex) { ... }
动态对象访问不存在的属性时,会抛出RuntimeBinderException(运行时错误),必须用try-catch捕获。
基础知识拓展
- 匿名类型vs动态对象核心区别
| 特性 | 匿名类型 | 动态对象 |
|---|---|---|
| 类型安全性 | 编译时检查(安全) | 运行时检查(不安全,易出错) |
| 属性可变性 | 只读(不可修改) | 可读写(动态添加/删除) |
| 性能 | 高(静态绑定,和普通类一致) | 低(运行时绑定,比静态类型慢2-10倍) |
| 适用场景 | 结构固定的临时数据(如查询中间结果) | 结构不确定的数据(如JSON、COM交互) |
| 序列化支持 | Newtonsoft.Json支持,System.Text.Json默认不支持 | 支持(JSON反序列化直接转为动态对象) |
-
匿名类型底层实现
编译器会为每个匿名类型生成密封类,类名是自动生成的(包含随机字符,避免冲突);
属性是public readonly,且类型是值类型或引用类型(根据赋值推断);
重写Equals/GetHashCode/ToString:ToString返回属性名和值的字符串(如{ Name = 张三, Age = 32 })。 -
动态对象的常见实现
ExpandoObject:.NET内置的动态对象,适合动态添加属性(本质是字典);
DynamicObject:抽象类,可自定义动态对象的行为(如重写TryGetMember/TrySetMember实现属性访问逻辑);
JObject:Newtonsoft.Json提供的动态对象,用于JSON处理。 -
动态对象的性能影响
动态对象的属性访问是运行时绑定(通过RuntimeBinder),比静态类型慢;
若需频繁访问动态对象的属性,建议转为静态类型(如用JsonConvert.DeserializeObject);
适合低频、结构不确定的场景(如JSON反序列化、COM组件调用)。
扩展思考
1.匿名类型序列化:System.Text.Json默认不支持匿名类型序列化,如何解决?(提示:用JsonSerializer.Serialize(tempUser, new JsonSerializerOptions { IncludeFields = true })或Newtonsoft.Json);
2.动态对象转静态类型:如何将动态对象转为已知的实体类?(提示:用JsonConvert.DeserializeObject(JsonConvert.SerializeObject(dynamicUser)));
3.匿名类型vs元组:匿名类型(var user = new { Name = "张三" })和元组(var user = ("张三",32))的区别?(元组更轻量,支持解构,适合临时传递多个值;匿名类型适合有明确属性名的场景);
4.自定义动态对象:继承DynamicObject,实现一个能自动转换属性类型的动态对象(如将字符串“123”转为int)。
总结
匿名类型和动态对象是C#中处理临时数据的高效工具:
匿名类型:强类型、只读、性能高,适合结构固定的临时数据;
动态对象:弱类型、可读写、灵活,适合结构不确定的场景;
选择原则:优先用匿名类型(类型安全),只有当结构不确定时才用动态对象(注意错误处理)。
合理使用两者可以减少冗余代码,提高开发效率,但要注意动态对象的类型安全和性能问题。
本站原创,转载请注明出处:










