personal-csharp
Compare original and translation side by side
🇺🇸
Original
English🇨🇳
Translation
ChineseC# Conventions
C#编码规范
Personal C# style, structure, and runtime conventions in one place: how code is shaped (naming, layout, syntax) and how it behaves (async, I/O, exceptions, logging, DI). Style is enforced by (Allman braces, 120-char line limit, file-scoped namespaces) and .
.editorconfigEnforceCodeStyleInBuild=trueSpecialized concerns route elsewhere: performance / memory layout -> ; concurrency primitives (, , ) -> ; the router maps the rest (EF, serialization, DI registration, config binding, DDD, packaging, tests).
type-design-performanceChannellockSemaphoreSlimcsharp-concurrency-patternspersonal-dotnet将个人C#风格、结构和运行时规范整合一处:涵盖代码的形态(命名、布局、语法)与行为(异步、I/O、异常、日志、DI)。代码风格通过(Allman大括号、120字符行宽限制、文件级命名空间)和强制执行。
.editorconfigEnforceCodeStyleInBuild=true专项内容请参考其他规范:性能/内存布局 -> ;并发原语(、、)-> ;其余内容(EF、序列化、DI注册、配置绑定、DDD、打包、测试)由路由规范覆盖。
type-design-performanceChannellockSemaphoreSlimcsharp-concurrency-patternspersonal-dotnetStyle and Structure
风格与结构
File structure
文件结构
- Max 300 lines per file. Split by extracting cohesive groups of methods into new classes.
- Max 120 chars per line in files. Count visible characters before committing. Markdown, JSON, config files exempt.
.cs - Partial classes only for generated code (EF migrations, designer files) or extending a generated class.
- One top-level type per file. File name must match the type name exactly.
- File-scoped namespaces: - never the braced form.
namespace MyApp.Services;
- 单个文件最多300行。可将内聚的方法组提取为新类来拆分文件。
- 文件每行最多120个字符。提交前统计可见字符数。Markdown、JSON、配置文件不受此限制。
.cs - 分部类仅用于生成代码(EF迁移、设计器文件)或扩展生成类。
- 每个文件仅包含一个顶级类型。文件名必须与类型名完全匹配。
- 文件级命名空间:使用形式——绝不使用带大括号的命名空间。
namespace MyApp.Services;
Naming
命名规则
- for private instance fields and
_camelCasefields.private static readonly - for public members, types, events, and every
PascalCaseregardless of accessibility. Aconstisprivate const; aPascalCaseinside a method body is alsoconst.PascalCase - for non-
camelCaselocal variables and method parameters.const - Prefix interfaces with :
I, notIOrderService.OrderService - Suffix async methods with :
Async.GetOrderAsync - No abbreviations unless universally accepted (,
Id,Dto,Url).Http - Boolean members read as a statement: ,
isReady,hasItems- notcanExecute,flag,status.enabled
Naming intent - apply four tests to every name:
- Domain-aligned - use vocabulary from the project domain. Avoid ,
Manager,Helper,Data,Info, or vague verbs likeItem/Processwhen a domain-specific term exists.Handle - Intent-revealing - the name explains what the member does without reading the implementation.
- DDD-consistent - value objects model concepts, not primitives. Don't suffix entity types with or
Entity. Do suffix repositories and services.Aggregate - Free of misleading names - a method named must persist; a
Savemethod must not also mutate state.Validate
- 私有实例字段和字段使用
private static readonly命名。_camelCase - 公共成员、类型、事件以及所有(无论访问级别)使用
const命名。PascalCase和方法体内的private const也使用const。PascalCase - 非局部变量和方法参数使用
const命名。camelCase - 接口以为前缀:如
I,而非IOrderService。OrderService - 异步方法以为后缀:如
Async。GetOrderAsync - 除非是通用缩写(如、
Id、Dto、Url),否则禁止使用缩写。Http - 布尔型成员名称需表述为陈述语句:如、
isReady、hasItems——而非canExecute、flag、status。enabled
命名原则 - 对每个名称应用四项测试:
- 领域对齐 - 使用项目领域的词汇。当存在领域特定术语时,避免使用、
Manager、Helper、Data、Info或模糊动词如Item/Process。Handle - 意图明确 - 名称无需查看实现即可解释成员的功能。
- DDD一致 - 值对象建模概念而非原始类型。实体类型无需后缀或
Entity。仓储和服务类型需添加后缀。Aggregate - 无误导性 - 名为的方法必须执行持久化操作;
Save方法不得同时修改状态。Validate
Class member ordering
类成员顺序
Enforced by . Order: private constants/statics, private readonly, private fields, protected/public properties, constructors, public/protected/private methods. Public properties before the constructor.
.editorconfig由强制执行。顺序:私有常量/静态成员、私有只读字段、私有字段、受保护/公共属性、构造函数、公共/受保护/私有方法。公共属性需置于构造函数之前。
.editorconfigConstructor parameter ordering
构造函数参数顺序
Private readonly fields, constructor parameters, and constructor body assignments must follow the same order.
Group order:
- /
ILogger- always first.ILogger<T> - Other interfaces.
- Classes (including sealed records, delegates such as , concrete service types).
Func<> - Structs (value types).
Within each group, order by scope, broadest first. Required before optional - all defaulted params trail required ones.
私有只读字段、构造函数参数和构造函数体赋值必须遵循相同顺序。
分组顺序:
- /
ILogger- 始终排在第一位。ILogger<T> - 其他接口。
- 类(包括密封记录、委托如、具体服务类型)。
Func<> - 结构体(值类型)。
每个分组内按作用域从广到窄排序。必填参数在前,可选参数在后——所有带默认值的参数均位于必填参数之后。
Blank lines
空行规则
Enforced by / formatter. The non-mechanical rule: one blank line before control-transfer statements (, , , etc.) when preceded by another statement - so the exit visually separates from preceding logic.
.editorconfigreturnthrowbreak由/格式化工具强制执行。非机械规则:当控制转移语句(、、等)之前有其他语句时,需在其前添加一行空行——使退出逻辑与前置代码在视觉上分离。
.editorconfigreturnthrowbreakMethods
方法规范
- Max 20 lines per method body. Refactor if exceeded.
- Max 3 parameters. Use a parameter object (record or class) for more.
- Methods do one thing. If 'and' appears in a method name, split it.
- No or
outparameters - return a tuple or result object instead.ref - Every case body wrapped in its own
switchblock - even when one statement, even when no variable is declared. Brace any half-braced switch you edit. Example:{ }
csharp
switch (x)
{
case A:
{
DoA();
break;
}
case B:
{
var y = Compute();
Use(y);
return;
}
default:
{
return;
}
}Per-case braces give each case its own scope (no accidental variable leak); blank line before / when preceded by another statement; no blank line when the transfer is the only statement after (the above).
breakreturn{default- 方法体最多20行。超出则需重构。
- 最多3个参数。参数更多时使用参数对象(记录或类)。
- 方法仅做一件事。如果方法名称中出现“and”,则需拆分方法。
- 禁止使用或
out参数——改用元组或结果对象返回。ref - 每个分支体都需包裹在独立的
switch块中——即使只有一条语句、未声明变量也需如此。编辑任何半带大括号的{ }时,都需补充完整大括号。示例:switch
csharp
switch (x)
{
case A:
{
DoA();
break;
}
case B:
{
var y = Compute();
Use(y);
return;
}
default:
{
return;
}
}每个分支的大括号为其提供独立作用域(避免变量意外泄漏);当/之前有其他语句时,需在其前添加空行;如果转移语句是后的唯一语句(如上述分支),则无需添加空行。
breakreturn{defaultTypes and variables
类型与变量
- Use only when the type is obvious from the right-hand side:
varOK;var order = new Order();not OK.var result = GetResult(); - Nullable reference types enabled project-wide; treat nullable warnings as errors.
- Prefer for immutable DTOs, value-like data, and types defined by their values. Prefer
recordwhen the type has identity, mutable state, inheritance, or behavior beyond data.class - Value objects: model as small immutable types - typically - validate in the constructor (trust everywhere after), and expose explicit conversions / factory methods only, never an
readonly record struct(it silently defeats the type safety it exists to provide). Add aimplicit operatorwhen the value object must bind from configuration.TypeConverter - Member signatures expose the narrowest useful shape: accept /
IEnumerable<T>/IReadOnlyCollection<T>(orIReadOnlyList<T>on hot paths), and return a read-only collection type (ReadOnlySpan<T>,IReadOnlyList<T>); return aIReadOnlyDictionary<,>/ array only when the caller is meant to mutate it.List<T> - No magic numbers or magic strings - use named constants or enums.
- Enums: explicit underlying values for any enum persisted to a database or sent over the wire. Use only when bitwise combination is intended.
[Flags] - Expression-bodied members only for single-expression getters and trivial methods.
- No public mutable fields - use properties.
- String comparison: always specify for non-linguistic comparisons (identifiers, keys, file paths),
StringComparison.Ordinalfor case-insensitive. Never rely on culture-default comparison.StringComparison.OrdinalIgnoreCase
- 仅当右侧类型明显时使用:
var是允许的;var order = new Order();不允许。var result = GetResult(); - 项目全局启用可为空引用类型;将可为空警告视为错误。
- 不可变DTO、类值数据和基于值定义的类型优先使用。当类型具有标识、可变状态、继承或数据之外的行为时,优先使用
record。class - 值对象:建模为小型不可变类型——通常是——在构造函数中进行验证(验证后可在任何地方信任),仅暴露显式转换/工厂方法,绝不使用
readonly record struct(它会悄无声息地破坏其旨在提供的类型安全性)。当值对象必须从配置绑定数据时,添加implicit operator。TypeConverter - 成员签名暴露最窄的有用形态:接受/
IEnumerable<T>/IReadOnlyCollection<T>(或热路径上的IReadOnlyList<T>),返回只读集合类型(ReadOnlySpan<T>、IReadOnlyList<T>);仅当调用者需要修改时才返回IReadOnlyDictionary<,>/数组。List<T> - 禁止使用魔法数字或魔法字符串——使用命名常量或枚举。
- 枚举:任何持久化到数据库或通过网络传输的枚举都需指定显式基础值。仅当需要按位组合时使用。
[Flags] - 表达式体成员仅用于单表达式属性 getter 和 trivial 方法。
- 禁止公共可变字段——使用属性。
- 字符串比较:非语言比较(标识符、键、文件路径)始终指定,不区分大小写的比较使用
StringComparison.Ordinal。绝不依赖区域性默认比较。StringComparison.OrdinalIgnoreCase
Visibility and sealing
可见性与密封
- Default to the lowest visibility that works: for class members,
privatefor assembly-scoped types,internalonly for cross-assembly API.public - Mark new classes unless inheritance is part of the design. Sealed classes enable JIT devirtualization and signal intent.
sealed - Mark methods or
virtualonly when overriding is genuinely required. Prefer composition over inheritance.abstract - Static classes only for pure utilities (no state, no I/O, no DI dependencies). For anything else, use a regular class with DI.
- Static fields only for true constants or thread-safe caches. Mutable static state is forbidden.
- 默认使用可行的最低可见性:类成员默认,程序集范围类型默认
private,仅跨程序集API使用internal。public - 新类默认标记为,除非设计中包含继承。密封类支持JIT去虚拟化并明确设计意图。
sealed - 仅当确实需要重写时,才标记方法为或
virtual。优先使用组合而非继承。abstract - 静态类仅用于纯工具类(无状态、无I/O、无DI依赖)。其他场景使用带DI的常规类。
- 静态字段仅用于真正的常量或线程安全缓存。禁止可变静态状态。
Modern C# syntax preferences (11+/12+/13)
现代C#语法偏好(11+/12+/13)
- Primary constructors - banned. Explicit ctor + assignment keeps member-ordering and
_fieldfield convention visible._camelCase - Collection expressions , spread
[a, b, c]- default for literal construction. Use[..first, last]only when items are added conditionally. Do not writenew List<T>()in new code.new[] { ... } - - default for new internal APIs (zero-alloc). Use
params ReadOnlySpan<T>only when the caller already owns an array.params T[] - Raw strings /
"""..."""- use for multi-line literals and for any string containing$$"""...""". Drop"for new code unless single-line and short.@"..." - members (C# 11) - use for properties that must be set during initialization but cannot be enforced by a constructor (e.g. records or DTOs with many properties).
required - keyword in accessors (C# 13) - allowed only for trivial guards. Anything richer keeps an explicit backing field.
field - (C# 13) - use for new lock objects. Do not retrofit existing
System.Threading.Locksites.lock(object) - Switch expression vs switch statement - prefer switch expression for value-returning code. Use switch statement only when arms have side effects (DI registration, channel writes, logging).
Performance concerns (sealing, readonly structs, / / , collection choice) belong with the skill.
Span<T>Memory<T>ArrayPool<T>type-design-performance- 主构造函数 - 禁止使用。显式构造函数+赋值可保持成员顺序和
_field字段规范的可见性。_camelCase - 集合表达式、展开运算符
[a, b, c]- 字面量构造的默认方式。仅当需要条件添加项时使用[..first, last]。新代码中禁止编写new List<T>()。new[] { ... } - - 新内部API的默认选择(零分配)。仅当调用者已拥有数组时使用
params ReadOnlySpan<T>。params T[] - 原始字符串/
"""..."""- 用于多行字面量和包含$$"""..."""的字符串。新代码中放弃使用",除非是单行短字符串。@"..." - 成员(C# 11)- 用于初始化期间必须设置但无法通过构造函数强制执行的属性(如具有多个属性的记录或DTO)。
required - 访问器中的关键字(C# 13)- 仅允许用于简单守卫逻辑。复杂逻辑需保留显式后备字段。
field - (C# 13)- 用于新的锁对象。无需改造现有
System.Threading.Lock代码。lock(object) - Switch表达式vs Switch语句 - 值返回代码优先使用switch表达式。仅当分支有副作用(DI注册、通道写入、日志记录)时使用switch语句。
性能相关内容(密封、只读结构体、//、集合选择)属于规范范畴。
Span<T>Memory<T>ArrayPool<T>type-design-performanceForbidden patterns
禁用模式
- No blocks.
#region - No for non-utility classes.
using static - No commented-out code - delete it.
- No without an associated ticket reference.
TODO - No reflection in business or hot-path code; use source generators or compile-time alternatives. No object-mapping libraries (AutoMapper / Mapster / ExpressMapper) - write explicit mapping methods (compile-time checked, debuggable, refactor-safe). Reflection is acceptable only in serialization, the DI container, ORM / EF, test infrastructure, or one-time bootstrap - never for DTO / domain mapping. When you must reach a private member (serializer, test helper), use (.NET 8), not
UnsafeAccessorAttribute.System.Reflection
When a convention here drives a package change - adding, removing, or swapping one (e.g. dropping a banned mapper, replacing Newtonsoft with System.Text.Json) - the install itself follows : use the CLI, never hand-edit .
package-managementdotnetDirectory.Packages.props- No - use
dynamic+ pattern matching or a typed interface.object - No top-level statements outside .
Program.cs
- 禁止使用块。
#region - 禁止对非工具类使用。
using static - 禁止注释掉的代码——直接删除。
- 禁止无关联工单引用的。
TODO - 业务代码或热路径中禁止使用反射;使用源代码生成器或编译时替代方案。禁止使用对象映射库(AutoMapper/Mapster/ExpressMapper)——编写显式映射方法(编译时检查、可调试、重构安全)。仅在序列化、DI容器、ORM/EF、测试基础设施或一次性启动代码中允许使用反射——绝不用于DTO/领域映射。当必须访问私有成员(序列化器、测试助手)时,使用(.NET 8+),而非
UnsafeAccessorAttribute。System.Reflection
当本规范驱动包变更(添加、移除或替换包,例如禁用映射库、用System.Text.Json替换Newtonsoft)时,安装操作遵循规范:使用 CLI,绝不手动编辑。
package-managementdotnetDirectory.Packages.props- 禁止使用——使用
dynamic+模式匹配或类型化接口。object - 之外禁止使用顶级语句。
Program.cs
Documentation
文档规范
- Every public API surface has XML doc comments covering parameters, return values, thrown exceptions, and remarks for non-obvious behavior.
- 每个公共API表面都需包含XML文档注释,涵盖参数、返回值、抛出异常,以及非明显行为的备注。
Runtime and Behavior
运行时与行为
Behavior, I/O, and composition rules.
行为、I/O和组合规则。
DateTime and timezones
DateTime与时区
- Store and pass , not
DateTimeOffset, for any value crossing process or DB boundaries.DateTime - All persisted timestamps in UTC. Convert to local only at the presentation boundary.
- Never call or
DateTime.Nowdirectly in business logic. Inject anDateTime.UtcNow/IClockabstraction (orISystemClockon .NET 8+).TimeProvider - Never call for measurements - use
DateTime.Now.Stopwatch
- 任何跨进程或数据库边界的值,存储和传递时使用,而非
DateTimeOffset。DateTime - 所有持久化时间戳均为UTC时间。仅在展示层转换为本地时间。
- 业务逻辑中绝不直接调用或
DateTime.Now。注入DateTime.UtcNow/IClock抽象(或.NET 8+上的ISystemClock)。TimeProvider - 测量耗时绝不调用——使用
DateTime.Now。Stopwatch
Async
异步操作
- Async all the way. No ,
.Result, or.Wait()in async code paths..GetAwaiter().GetResult() - Return , not
Task, for async methods (except event handlers).void - Always pass and forward for I/O-bound or long-running operations.
CancellationToken - No except event handlers; mark those clearly.
async void - Use in library code; ignore it in ASP.NET Core application code (no sync context).
ConfigureAwait(false) - Use /
ValueTaskonly on hot paths where synchronous completion is common and benchmarks show allocation pressure. Default toValueTask<T>.Task - Return for streaming results (paged DB reads, long-running enumerations). Annotate the
IAsyncEnumerable<T>parameter withCancellationToken.[EnumeratorCancellation]
- 全程异步。异步代码路径中禁止使用、
.Result或.Wait()。.GetAwaiter().GetResult() - 异步方法返回,而非
Task(事件处理程序除外)。void - I/O绑定或长时间运行的操作始终传递并转发。
CancellationToken - 除事件处理程序外,禁止使用;事件处理程序需明确标记。
async void - 库代码中使用;ASP.NET Core应用代码中可忽略(无同步上下文)。
ConfigureAwait(false) - 仅在热路径(同步完成常见且基准测试显示分配压力)上使用/
ValueTask。默认使用ValueTask<T>。Task - 流式结果(分页数据库读取、长时间枚举)返回。为
IAsyncEnumerable<T>参数添加CancellationToken注解。[EnumeratorCancellation]
Dispose pattern
释放模式
- Use declarations (
using) overusing var x = ...;blocks where scope allows.using - Implement for types holding async resources. Implement both
IAsyncDisposableandIDisposablewhen both sync and async disposal paths are realistic.IAsyncDisposable - Never call on injected dependencies - the DI container owns their lifetime.
Dispose() - Use the full Dispose pattern () only for unmanaged resources or when inheritance is in play. Otherwise a simple
protected virtual Dispose(bool disposing)is enough.Dispose()
- 作用域允许时,优先使用声明(
using)而非using var x = ...;块。using - 持有异步资源的类型需实现。当同步和异步释放路径均可行时,同时实现
IAsyncDisposable和IDisposable。IAsyncDisposable - 绝不调用注入依赖的——DI容器管理其生命周期。
Dispose() - 仅当涉及非托管资源或继承时,才使用完整的释放模式()。否则简单实现
protected virtual Dispose(bool disposing)即可。Dispose()
Exception handling and Result pattern
异常处理与Result模式
- Distinguish expected outcomes from exceptional failures. Validation, not-found, and business-rule failures are expected - return a result type rather than throwing. Prefer a domain-specific result (a sealed record with /
Successfactory methods and an error-code enum, e.g.Failed) over a genericCreateOrderResult/Result<T>when the operation's failure modes are known.OneOf<,> - Exceptions for unexpected failures only (I/O errors, programming errors, contract violations).
- Catch specific exceptions; never bare in business logic unless logging and re-throwing.
catch (Exception) - Do not use exceptions for control flow.
- Re-throw with not
throw;(preserves stack trace).throw ex; - Validate arguments at the top of public methods. Prefer the static throw-helpers over hand-written guards: ,
ArgumentNullException.ThrowIfNull(x),ArgumentException.ThrowIfNullOrWhiteSpace(s)/ArgumentOutOfRangeException.ThrowIfNegative(.NET 8).ThrowIfGreaterThan(...)
- 区分预期结果与异常失败。验证失败、未找到、业务规则失败属于预期情况——返回结果类型而非抛出异常。当操作的失败模式已知时,优先使用领域特定结果(带/
Success工厂方法和错误码枚举的密封记录,如Failed)而非通用CreateOrderResult/Result<T>。OneOf<,> - 异常仅用于意外失败(I/O错误、编程错误、契约违反)。
- 捕获特定异常;业务逻辑中绝不使用裸,除非用于记录日志并重新抛出。
catch (Exception) - 禁止将异常用于控制流。
- 使用重新抛出,而非
throw;(保留堆栈跟踪)。throw ex; - 公共方法顶部验证参数。优先使用静态抛出助手而非手写守卫:、
ArgumentNullException.ThrowIfNull(x)、ArgumentException.ThrowIfNullOrWhiteSpace(s)/ArgumentOutOfRangeException.ThrowIfNegative(.NET 8+)。ThrowIfGreaterThan(...)
Logging
日志规范
- Structured logging via . Use templates with named placeholders:
ILogger<T>. Never use string interpolation in log calls._logger.LogInformation('Order {OrderId} placed for {UserId}', orderId, userId) - Log levels: (diagnostic noise),
Trace(dev),Debug(business events),Information(recoverable issue),Warning(operation failed),Error(system unusable).Critical - Log exceptions with the exception object as the first arg: . Never
_logger.LogError(ex, 'Failed to {Action}', actionName)an exception into the message..ToString() - Never log: passwords, tokens, secrets, full payment data, PII beyond what is operationally needed. For healthcare and e-commerce projects, treat full identifiers as PII.
- One log statement per logical event. Avoid log spam in tight loops.
- 通过实现结构化日志。使用带命名占位符的模板:
ILogger<T>。日志调用中绝不使用字符串插值。_logger.LogInformation("Order {OrderId} placed for {UserId}", orderId, userId) - 日志级别:(诊断噪音)、
Trace(开发调试)、Debug(业务事件)、Information(可恢复问题)、Warning(操作失败)、Error(系统不可用)。Critical - 记录异常时,将异常对象作为第一个参数:。绝不将异常
_logger.LogError(ex, "Failed to {Action}", actionName)后放入消息中。.ToString() - 绝不记录:密码、令牌、密钥、完整支付数据、超出运营需求的PII。医疗和电商项目中,完整标识符视为PII。
- 每个逻辑事件对应一条日志。避免在循环中生成大量日志。
Secrets and configuration sources
密钥与配置源
- Secrets never live in . Local development uses
appsettings.json. Production uses environment variables, Azure Key Vault, AWS Secrets Manager, or platform equivalents.dotnet user-secrets - Configuration layering: (defaults) ->
appsettings.json-> environment variables -> command-line args. Later layers override earlier.appsettings.{Environment}.json
Typed options binding ( / / ) and startup validation (, , data-annotation validation) live in - consult it for those, do not restate here.
IOptions<T>IOptionsSnapshot<T>IOptionsMonitor<T>ValidateOnStartIValidateOptions<T>microsoft-extensions-configuration- 密钥绝不存储在中。本地开发使用
appsettings.json。生产环境使用环境变量、Azure Key Vault、AWS Secrets Manager或平台等效服务。dotnet user-secrets - 配置分层:(默认值)->
appsettings.json-> 环境变量 -> 命令行参数。后续层覆盖先前层。appsettings.{Environment}.json
类型化选项绑定(//)和启动验证(、、数据注解验证)属于规范范畴——请参考该规范,此处不再赘述。
IOptions<T>IOptionsSnapshot<T>IOptionsMonitor<T>ValidateOnStartIValidateOptions<T>microsoft-extensions-configurationLINQ
LINQ规范
- Method syntax (,
Where,Select), not query syntax, unless query syntax is materially clearer (e.g. multi-join queries).GroupBy - No more than 4-5 chained operators without an intermediate variable with a descriptive name.
- Materialize queries (,
ToList) before returning from a method that owns the DbContext or connection lifetime.ToArray
- 优先使用方法语法(、
Where、Select),而非查询语法,除非查询语法明显更清晰(如多连接查询)。GroupBy - 链式运算符不超过4-5个,否则需使用带描述性名称的中间变量。
- 在拥有DbContext或连接生命周期的方法返回前,需将查询具体化(、
ToList)。ToArray
JSON serialization
JSON序列化
- is the default. Newtonsoft.Json only for legacy compatibility or features missing from STJ (e.g. polymorphic serialization in older runtimes).
System.Text.Json - Configure once and reuse - never construct per call.
JsonSerializerOptions - Naming policy: for external APIs unless a contract requires otherwise.
JsonNamingPolicy.CamelCase - Use source-generated for hot paths and AOT compatibility.
JsonSerializerContext - Never deserialize untrusted JSON without size and depth limits.
- 默认使用。仅在遗留兼容性或STJ缺失特性(如旧运行时中的多态序列化)时使用Newtonsoft.Json。
System.Text.Json - 配置一次并复用——绝不每次调用都构造新实例。
JsonSerializerOptions - 命名策略:外部API默认使用,除非契约另有要求。
JsonNamingPolicy.CamelCase - 热路径和AOT兼容性场景使用源代码生成的。
JsonSerializerContext - 反序列化不可信JSON时,必须设置大小和深度限制。
Decoupling and DI lifetimes
解耦与DI生命周期
- Never call on service-layer or infrastructure types inside a class body - use factories or DI.
new - No circular dependencies between namespaces.
- Never inject a shorter-lifetime service into a longer-lifetime one (captive dependency). Use or a
IServiceScopeFactoryfactory for cross-lifetime access.Func<T>
- 类体内绝不直接实例化服务层或基础设施类型——使用工厂或DI。
- 命名空间之间禁止循环依赖。
- 绝不将短生命周期服务注入长生命周期服务(捕获依赖)。跨生命周期访问使用或
IServiceScopeFactory工厂。Func<T>