You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何填充含受保护构造函数与私有属性设置器的对象?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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.04.29 05:27:53