如何填充含受保护构造函数与私有属性设置器的对象?Dapper/JsonConvert实现原理解析
嘿,这个问题问到点子上了——既涉及到领域驱动设计中对象封装的实践,又牵扯到ORM和序列化工具的底层实现逻辑。我来分两部分给你讲清楚:
一、不依赖ORM框架,手动构造Document对象
你的Document类设计很符合DDD的封装原则:私有属性设置器,公开的带参数构造函数(用来保证领域规则),还有一个受保护的无参构造(通常给ORM留的后门)。手动构造的话,最推荐的方式是直接使用公开的带参数构造函数,这既能保证领域规则不被绕过,又简单直接。
举个例子,假设你从关系型数据库手动读取数据:
public Document GetDocumentFromStorage(string connectionString, string targetIndex) { // 1. 从存储介质读取原始数据(这里以SQL Server为例) using var connection = new SqlConnection(connectionString); connection.Open(); var query = @"SELECT Index, Title, Content FROM Documents WHERE Index = @TargetIndex"; using var command = new SqlCommand(query, connection); command.Parameters.AddWithValue("@TargetIndex", targetIndex); using var reader = command.ExecuteReader(); if (!reader.Read()) return null; // 2. 提取字段值 var index = reader.GetString(reader.GetOrdinal("Index")); var title = reader.GetString(reader.GetOrdinal("Title")); var content = reader.GetString(reader.GetOrdinal("Content")); // 3. 调用公开构造函数,自动遵守领域规则 return new Document(index, title, content); }
如果你的存储介质是JSON文件,逻辑也类似:先读取JSON字符串,解析出三个字段,再调用构造函数即可。
当然,如果你实在需要绕开公开构造(不推荐,因为会破坏领域规则),也可以用反射调用受保护的无参构造,再设置私有属性:
public Document CreateDocumentViaReflection(string index, string title, string content) { // 获取受保护的无参构造函数 var constructor = typeof(Document) .GetConstructor(BindingFlags.Instance | BindingFlags.NonPublic, Type.EmptyTypes); if (constructor == null) throw new InvalidOperationException("无参构造函数不存在"); var document = (Document)constructor.Invoke(null); // 反射设置私有属性 typeof(Document).GetProperty(nameof(Document.Index)) ?.SetValue(document, index, BindingFlags.Instance | BindingFlags.NonPublic, null, null, null); typeof(Document).GetProperty(nameof(Document.Title)) ?.SetValue(document, title, BindingFlags.Instance | BindingFlags.NonPublic, null, null, null); typeof(Document).GetProperty(nameof(Document.Content)) ?.SetValue(document, content, BindingFlags.Instance | BindingFlags.NonPublic, null, null, null); return document; }
⚠️ 注意:这种方式会绕过Document构造函数里的领域规则,可能导致对象处于无效状态,除非你手动重新校验规则,否则不建议使用。
二、Dapper和JsonConvert如何操作私有成员?
核心原理都是利用.NET反射(或IL生成)突破访问修饰符的限制,具体细节略有不同:
1. Dapper的实现逻辑
Dapper为了兼顾性能和灵活性,会做这些操作:
- 构造函数选择:Dapper会优先查找与查询字段匹配的构造函数(包括非公共的),如果没有匹配的,就尝试使用无参构造函数(哪怕是protected的)。它通过
Type.GetConstructors(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic)获取所有构造函数,然后筛选最合适的。 - 属性赋值:对于私有属性,Dapper会通过反射获取属性的
SetMethod(即使是private的),然后缓存这个方法的委托。为了避免反射的性能开销,Dapper会用**IL生成(Emit)**动态创建赋值代码,直接操作对象的属性或字段,相当于在运行时生成一段绕过访问限制的代码,比每次反射快很多。
简单来说,Dapper会先“探知”你的类结构,然后生成高效的代码来实例化对象并赋值,完全不care属性是private还是构造是protected。
2. JsonConvert(Newtonsoft.Json)的实现逻辑
JsonConvert默认就支持访问非公共成员,核心靠的是ContractResolver:
- 构造函数处理:它会遍历类型的所有构造函数(包括protected/private),选择最合适的无参或带参构造来实例化对象。
- 属性访问:通过
BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic来获取所有属性,包括私有属性的设置器。你也可以通过配置JsonSerializerSettings来控制是否允许访问非公共成员:
var settings = new JsonSerializerSettings { ContractResolver = new DefaultContractResolver { // 允许搜索非公共成员 DefaultMembersSearchFlags = BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic } }; var document = JsonConvert.DeserializeObject<Document>(jsonContent, settings);
和Dapper类似,Newtonsoft.Json也会缓存序列化/反序列化的契约信息,避免重复反射,提升性能。
内容的提问来源于stack exchange,提问作者Navid_pdp11
相关产品推荐
相关产品推荐

