personal-csharp

Compare original and translation side by side

🇺🇸

Original

English
🇨🇳

Translation

Chinese

C# 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
.editorconfig
(Allman braces, 120-char line limit, file-scoped namespaces) and
EnforceCodeStyleInBuild=true
.
Specialized concerns route elsewhere: performance / memory layout ->
type-design-performance
; concurrency primitives (
Channel
,
lock
,
SemaphoreSlim
) ->
csharp-concurrency-patterns
; the
personal-dotnet
router maps the rest (EF, serialization, DI registration, config binding, DDD, packaging, tests).

将个人C#风格、结构和运行时规范整合一处:涵盖代码的形态(命名、布局、语法)与行为(异步、I/O、异常、日志、DI)。代码风格通过
.editorconfig
(Allman大括号、120字符行宽限制、文件级命名空间)和
EnforceCodeStyleInBuild=true
强制执行。
专项内容请参考其他规范:性能/内存布局 ->
type-design-performance
;并发原语(
Channel
lock
SemaphoreSlim
)->
csharp-concurrency-patterns
;其余内容(EF、序列化、DI注册、配置绑定、DDD、打包、测试)由
personal-dotnet
路由规范覆盖。

Style 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
    .cs
    files. Count visible characters before committing. Markdown, JSON, config files exempt.
  • 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:
    namespace MyApp.Services;
    - never the braced form.
  • 单个文件最多300行。可将内聚的方法组提取为新类来拆分文件。
  • .cs
    文件每行最多120个字符。提交前统计可见字符数。Markdown、JSON、配置文件不受此限制。
  • 分部类仅用于生成代码(EF迁移、设计器文件)或扩展生成类。
  • 每个文件仅包含一个顶级类型。文件名必须与类型名完全匹配。
  • 文件级命名空间:使用
    namespace MyApp.Services;
    形式——绝不使用带大括号的命名空间。

Naming

命名规则

  • _camelCase
    for private instance fields and
    private static readonly
    fields.
  • PascalCase
    for public members, types, events, and every
    const
    regardless of accessibility. A
    private const
    is
    PascalCase
    ; a
    const
    inside a method body is also
    PascalCase
    .
  • camelCase
    for non-
    const
    local variables and method parameters.
  • Prefix interfaces with
    I
    :
    IOrderService
    , not
    OrderService
    .
  • Suffix async methods with
    Async
    :
    GetOrderAsync
    .
  • No abbreviations unless universally accepted (
    Id
    ,
    Dto
    ,
    Url
    ,
    Http
    ).
  • Boolean members read as a statement:
    isReady
    ,
    hasItems
    ,
    canExecute
    - not
    flag
    ,
    status
    ,
    enabled
    .
Naming intent - apply four tests to every name:
  1. Domain-aligned - use vocabulary from the project domain. Avoid
    Manager
    ,
    Helper
    ,
    Data
    ,
    Info
    ,
    Item
    , or vague verbs like
    Process
    /
    Handle
    when a domain-specific term exists.
  2. Intent-revealing - the name explains what the member does without reading the implementation.
  3. DDD-consistent - value objects model concepts, not primitives. Don't suffix entity types with
    Entity
    or
    Aggregate
    . Do suffix repositories and services.
  4. Free of misleading names - a method named
    Save
    must persist; a
    Validate
    method must not also mutate state.
  • 私有实例字段和
    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
命名原则 - 对每个名称应用四项测试:
  1. 领域对齐 - 使用项目领域的词汇。当存在领域特定术语时,避免使用
    Manager
    Helper
    Data
    Info
    Item
    或模糊动词如
    Process
    /
    Handle
  2. 意图明确 - 名称无需查看实现即可解释成员的功能。
  3. DDD一致 - 值对象建模概念而非原始类型。实体类型无需后缀
    Entity
    Aggregate
    。仓储和服务类型需添加后缀。
  4. 无误导性 - 名为
    Save
    的方法必须执行持久化操作;
    Validate
    方法不得同时修改状态。

Class member ordering

类成员顺序

Enforced by
.editorconfig
. Order: private constants/statics, private readonly, private fields, protected/public properties, constructors, public/protected/private methods. Public properties before the constructor.
.editorconfig
强制执行。顺序:私有常量/静态成员、私有只读字段、私有字段、受保护/公共属性、构造函数、公共/受保护/私有方法。公共属性需置于构造函数之前。

Constructor parameter ordering

构造函数参数顺序

Private readonly fields, constructor parameters, and constructor body assignments must follow the same order.
Group order:
  1. ILogger
    /
    ILogger<T>
    - always first.
  2. Other interfaces.
  3. Classes (including sealed records, delegates such as
    Func<>
    , concrete service types).
  4. Structs (value types).
Within each group, order by scope, broadest first. Required before optional - all defaulted params trail required ones.
私有只读字段、构造函数参数和构造函数体赋值必须遵循相同顺序。
分组顺序:
  1. ILogger
    /
    ILogger<T>
    - 始终排在第一位。
  2. 其他接口。
  3. 类(包括密封记录、委托如
    Func<>
    、具体服务类型)。
  4. 结构体(值类型)。
每个分组内按作用域从广到窄排序。必填参数在前,可选参数在后——所有带默认值的参数均位于必填参数之后。

Blank lines

空行规则

Enforced by
.editorconfig
/ formatter. The non-mechanical rule: one blank line before control-transfer statements (
return
,
throw
,
break
, etc.) when preceded by another statement - so the exit visually separates from preceding logic.
.editorconfig
/格式化工具强制执行。非机械规则:当控制转移语句(
return
throw
break
等)之前有其他语句时,需在其前添加一行空行——使退出逻辑与前置代码在视觉上分离。

Methods

方法规范

  • 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
    out
    or
    ref
    parameters - return a tuple or result object instead.
  • Every
    switch
    case body wrapped in its own
    { }
    block - 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
break
/
return
when preceded by another statement; no blank line when the transfer is the only statement after
{
(the
default
above).
  • 方法体最多20行。超出则需重构。
  • 最多3个参数。参数更多时使用参数对象(记录或类)。
  • 方法仅做一件事。如果方法名称中出现“and”,则需拆分方法。
  • 禁止使用
    out
    ref
    参数——改用元组或结果对象返回。
  • 每个
    switch
    分支体都需包裹在独立的
    { }
    块中——即使只有一条语句、未声明变量也需如此。编辑任何半带大括号的
    switch
    时,都需补充完整大括号。示例:
csharp
switch (x)
{
    case A:
    {
        DoA();

        break;
    }
    case B:
    {
        var y = Compute();
        Use(y);

        return;
    }
    default:
    {
        return;
    }
}
每个分支的大括号为其提供独立作用域(避免变量意外泄漏);当
break
/
return
之前有其他语句时,需在其前添加空行;如果转移语句是
{
后的唯一语句(如上述
default
分支),则无需添加空行。

Types and variables

类型与变量

  • Use
    var
    only when the type is obvious from the right-hand side:
    var order = new Order();
    OK;
    var result = GetResult();
    not OK.
  • Nullable reference types enabled project-wide; treat nullable warnings as errors.
  • Prefer
    record
    for immutable DTOs, value-like data, and types defined by their values. Prefer
    class
    when the type has identity, mutable state, inheritance, or behavior beyond data.
  • Value objects: model as small immutable types - typically
    readonly record struct
    - validate in the constructor (trust everywhere after), and expose explicit conversions / factory methods only, never an
    implicit operator
    (it silently defeats the type safety it exists to provide). Add a
    TypeConverter
    when the value object must bind from configuration.
  • Member signatures expose the narrowest useful shape: accept
    IEnumerable<T>
    /
    IReadOnlyCollection<T>
    /
    IReadOnlyList<T>
    (or
    ReadOnlySpan<T>
    on hot paths), and return a read-only collection type (
    IReadOnlyList<T>
    ,
    IReadOnlyDictionary<,>
    ); return a
    List<T>
    / array only when the caller is meant to mutate it.
  • 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
    [Flags]
    only when bitwise combination is intended.
  • Expression-bodied members only for single-expression getters and trivial methods.
  • No public mutable fields - use properties.
  • String comparison: always specify
    StringComparison.Ordinal
    for non-linguistic comparisons (identifiers, keys, file paths),
    StringComparison.OrdinalIgnoreCase
    for case-insensitive. Never rely on culture-default comparison.
  • 仅当右侧类型明显时使用
    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:
    private
    for class members,
    internal
    for assembly-scoped types,
    public
    only for cross-assembly API.
  • Mark new classes
    sealed
    unless inheritance is part of the design. Sealed classes enable JIT devirtualization and signal intent.
  • Mark methods
    virtual
    or
    abstract
    only when overriding is genuinely required. Prefer composition over inheritance.
  • 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
    ,程序集范围类型默认
    internal
    ,仅跨程序集API使用
    public
  • 新类默认标记为
    sealed
    ,除非设计中包含继承。密封类支持JIT去虚拟化并明确设计意图。
  • 仅当确实需要重写时,才标记方法为
    virtual
    abstract
    。优先使用组合而非继承。
  • 静态类仅用于纯工具类(无状态、无I/O、无DI依赖)。其他场景使用带DI的常规类。
  • 静态字段仅用于真正的常量或线程安全缓存。禁止可变静态状态。

Modern C# syntax preferences (11+/12+/13)

现代C#语法偏好(11+/12+/13)

  • Primary constructors - banned. Explicit ctor +
    _field
    assignment keeps member-ordering and
    _camelCase
    field convention visible.
  • Collection expressions
    [a, b, c]
    , spread
    [..first, last]
    - default for literal construction. Use
    new List<T>()
    only when items are added conditionally. Do not write
    new[] { ... }
    in new code.
  • params ReadOnlySpan<T>
    - default for new internal APIs (zero-alloc). Use
    params T[]
    only when the caller already owns an array.
  • Raw strings
    """..."""
    /
    $$"""..."""
    - use for multi-line literals and for any string containing
    "
    . Drop
    @"..."
    for new code unless single-line and short.
  • required
    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).
  • field
    keyword in accessors
    (C# 13) - allowed only for trivial guards. Anything richer keeps an explicit backing field.
  • System.Threading.Lock
    (C# 13) - use for new lock objects. Do not retrofit existing
    lock(object)
    sites.
  • 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,
Span<T>
/
Memory<T>
/
ArrayPool<T>
, collection choice) belong with the
type-design-performance
skill.
  • 主构造函数 - 禁止使用。显式构造函数+
    _field
    赋值可保持成员顺序和
    _camelCase
    字段规范的可见性。
  • 集合表达式
    [a, b, c]
    、展开运算符
    [..first, last]
    - 字面量构造的默认方式。仅当需要条件添加项时使用
    new List<T>()
    。新代码中禁止编写
    new[] { ... }
  • params ReadOnlySpan<T>
    - 新内部API的默认选择(零分配)。仅当调用者已拥有数组时使用
    params T[]
  • 原始字符串
    """..."""
    /
    $$"""..."""
    - 用于多行字面量和包含
    "
    的字符串。新代码中放弃使用
    @"..."
    ,除非是单行短字符串。
  • required
    成员
    (C# 11)- 用于初始化期间必须设置但无法通过构造函数强制执行的属性(如具有多个属性的记录或DTO)。
  • 访问器中的
    field
    关键字
    (C# 13)- 仅允许用于简单守卫逻辑。复杂逻辑需保留显式后备字段。
  • System.Threading.Lock
    (C# 13)- 用于新的锁对象。无需改造现有
    lock(object)
    代码。
  • Switch表达式vs Switch语句 - 值返回代码优先使用switch表达式。仅当分支有副作用(DI注册、通道写入、日志记录)时使用switch语句。
性能相关内容(密封、只读结构体、
Span<T>
/
Memory<T>
/
ArrayPool<T>
、集合选择)属于
type-design-performance
规范范畴。

Forbidden patterns

禁用模式

  • No
    #region
    blocks.
  • No
    using static
    for non-utility classes.
  • No commented-out code - delete it.
  • No
    TODO
    without an associated ticket reference.
  • 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
    UnsafeAccessorAttribute
    (.NET 8), not
    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
package-management
: use the
dotnet
CLI, never hand-edit
Directory.Packages.props
.
  • No
    dynamic
    - use
    object
    + pattern matching or a typed interface.
  • No top-level statements outside
    Program.cs
    .
  • 禁止使用
    #region
    块。
  • 禁止对非工具类使用
    using static
  • 禁止注释掉的代码——直接删除。
  • 禁止无关联工单引用的
    TODO
  • 业务代码或热路径中禁止使用反射;使用源代码生成器或编译时替代方案。禁止使用对象映射库(AutoMapper/Mapster/ExpressMapper)——编写显式映射方法(编译时检查、可调试、重构安全)。仅在序列化、DI容器、ORM/EF、测试基础设施或一次性启动代码中允许使用反射——绝不用于DTO/领域映射。当必须访问私有成员(序列化器、测试助手)时,使用
    UnsafeAccessorAttribute
    (.NET 8+),而非
    System.Reflection
当本规范驱动包变更(添加、移除或替换包,例如禁用映射库、用System.Text.Json替换Newtonsoft)时,安装操作遵循
package-management
规范:使用
dotnet
CLI,绝不手动编辑
Directory.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
    DateTimeOffset
    , not
    DateTime
    , for any value crossing process or DB boundaries.
  • All persisted timestamps in UTC. Convert to local only at the presentation boundary.
  • Never call
    DateTime.Now
    or
    DateTime.UtcNow
    directly in business logic. Inject an
    IClock
    /
    ISystemClock
    abstraction (or
    TimeProvider
    on .NET 8+).
  • Never call
    DateTime.Now
    for measurements - use
    Stopwatch
    .
  • 任何跨进程或数据库边界的值,存储和传递时使用
    DateTimeOffset
    ,而非
    DateTime
  • 所有持久化时间戳均为UTC时间。仅在展示层转换为本地时间。
  • 业务逻辑中绝不直接调用
    DateTime.Now
    DateTime.UtcNow
    。注入
    IClock
    /
    ISystemClock
    抽象(或.NET 8+上的
    TimeProvider
    )。
  • 测量耗时绝不调用
    DateTime.Now
    ——使用
    Stopwatch

Async

异步操作

  • Async all the way. No
    .Result
    ,
    .Wait()
    , or
    .GetAwaiter().GetResult()
    in async code paths.
  • Return
    Task
    , not
    void
    , for async methods (except event handlers).
  • Always pass and forward
    CancellationToken
    for I/O-bound or long-running operations.
  • No
    async void
    except event handlers; mark those clearly.
  • Use
    ConfigureAwait(false)
    in library code; ignore it in ASP.NET Core application code (no sync context).
  • Use
    ValueTask
    /
    ValueTask<T>
    only on hot paths where synchronous completion is common and benchmarks show allocation pressure. Default to
    Task
    .
  • Return
    IAsyncEnumerable<T>
    for streaming results (paged DB reads, long-running enumerations). Annotate the
    CancellationToken
    parameter with
    [EnumeratorCancellation]
    .
  • 全程异步。异步代码路径中禁止使用
    .Result
    .Wait()
    .GetAwaiter().GetResult()
  • 异步方法返回
    Task
    ,而非
    void
    (事件处理程序除外)。
  • I/O绑定或长时间运行的操作始终传递并转发
    CancellationToken
  • 除事件处理程序外,禁止使用
    async void
    ;事件处理程序需明确标记。
  • 库代码中使用
    ConfigureAwait(false)
    ;ASP.NET Core应用代码中可忽略(无同步上下文)。
  • 仅在热路径(同步完成常见且基准测试显示分配压力)上使用
    ValueTask
    /
    ValueTask<T>
    。默认使用
    Task
  • 流式结果(分页数据库读取、长时间枚举)返回
    IAsyncEnumerable<T>
    。为
    CancellationToken
    参数添加
    [EnumeratorCancellation]
    注解。

Dispose pattern

释放模式

  • Use
    using
    declarations (
    using var x = ...;
    ) over
    using
    blocks where scope allows.
  • Implement
    IAsyncDisposable
    for types holding async resources. Implement both
    IDisposable
    and
    IAsyncDisposable
    when both sync and async disposal paths are realistic.
  • Never call
    Dispose()
    on injected dependencies - the DI container owns their lifetime.
  • Use the full Dispose pattern (
    protected virtual Dispose(bool disposing)
    ) only for unmanaged resources or when inheritance is in play. Otherwise a simple
    Dispose()
    is enough.
  • 作用域允许时,优先使用
    using
    声明(
    using var x = ...;
    )而非
    using
    块。
  • 持有异步资源的类型需实现
    IAsyncDisposable
    。当同步和异步释放路径均可行时,同时实现
    IDisposable
    IAsyncDisposable
  • 绝不调用注入依赖的
    Dispose()
    ——DI容器管理其生命周期。
  • 仅当涉及非托管资源或继承时,才使用完整的释放模式(
    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
    Success
    /
    Failed
    factory methods and an error-code enum, e.g.
    CreateOrderResult
    ) over a generic
    Result<T>
    /
    OneOf<,>
    when the operation's failure modes are known.
  • Exceptions for unexpected failures only (I/O errors, programming errors, contract violations).
  • Catch specific exceptions; never bare
    catch (Exception)
    in business logic unless logging and re-throwing.
  • Do not use exceptions for control flow.
  • Re-throw with
    throw;
    not
    throw ex;
    (preserves stack trace).
  • 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
    /
    ThrowIfGreaterThan(...)
    (.NET 8).
  • 区分预期结果与异常失败。验证失败、未找到、业务规则失败属于预期情况——返回结果类型而非抛出异常。当操作的失败模式已知时,优先使用领域特定结果(带
    Success
    /
    Failed
    工厂方法和错误码枚举的密封记录,如
    CreateOrderResult
    )而非通用
    Result<T>
    /
    OneOf<,>
  • 异常仅用于意外失败(I/O错误、编程错误、契约违反)。
  • 捕获特定异常;业务逻辑中绝不使用裸
    catch (Exception)
    ,除非用于记录日志并重新抛出。
  • 禁止将异常用于控制流。
  • 使用
    throw;
    重新抛出,而非
    throw ex;
    (保留堆栈跟踪)。
  • 公共方法顶部验证参数。优先使用静态抛出助手而非手写守卫:
    ArgumentNullException.ThrowIfNull(x)
    ArgumentException.ThrowIfNullOrWhiteSpace(s)
    ArgumentOutOfRangeException.ThrowIfNegative
    /
    ThrowIfGreaterThan(...)
    (.NET 8+)。

Logging

日志规范

  • Structured logging via
    ILogger<T>
    . Use templates with named placeholders:
    _logger.LogInformation('Order {OrderId} placed for {UserId}', orderId, userId)
    . Never use string interpolation in log calls.
  • Log levels:
    Trace
    (diagnostic noise),
    Debug
    (dev),
    Information
    (business events),
    Warning
    (recoverable issue),
    Error
    (operation failed),
    Critical
    (system unusable).
  • Log exceptions with the exception object as the first arg:
    _logger.LogError(ex, 'Failed to {Action}', actionName)
    . Never
    .ToString()
    an exception into the message.
  • 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
    appsettings.json
    . Local development uses
    dotnet user-secrets
    . Production uses environment variables, Azure Key Vault, AWS Secrets Manager, or platform equivalents.
  • Configuration layering:
    appsettings.json
    (defaults) ->
    appsettings.{Environment}.json
    -> environment variables -> command-line args. Later layers override earlier.
Typed options binding (
IOptions<T>
/
IOptionsSnapshot<T>
/
IOptionsMonitor<T>
) and startup validation (
ValidateOnStart
,
IValidateOptions<T>
, data-annotation validation) live in
microsoft-extensions-configuration
- consult it for those, do not restate here.
  • 密钥绝不存储在
    appsettings.json
    中。本地开发使用
    dotnet user-secrets
    。生产环境使用环境变量、Azure Key Vault、AWS Secrets Manager或平台等效服务。
  • 配置分层:
    appsettings.json
    (默认值)->
    appsettings.{Environment}.json
    -> 环境变量 -> 命令行参数。后续层覆盖先前层。
类型化选项绑定(
IOptions<T>
/
IOptionsSnapshot<T>
/
IOptionsMonitor<T>
)和启动验证(
ValidateOnStart
IValidateOptions<T>
、数据注解验证)属于
microsoft-extensions-configuration
规范范畴——请参考该规范,此处不再赘述。

LINQ

LINQ规范

  • Method syntax (
    Where
    ,
    Select
    ,
    GroupBy
    ), not query syntax, unless query syntax is materially clearer (e.g. multi-join queries).
  • No more than 4-5 chained operators without an intermediate variable with a descriptive name.
  • Materialize queries (
    ToList
    ,
    ToArray
    ) before returning from a method that owns the DbContext or connection lifetime.
  • 优先使用方法语法(
    Where
    Select
    GroupBy
    ),而非查询语法,除非查询语法明显更清晰(如多连接查询)。
  • 链式运算符不超过4-5个,否则需使用带描述性名称的中间变量。
  • 在拥有DbContext或连接生命周期的方法返回前,需将查询具体化(
    ToList
    ToArray
    )。

JSON serialization

JSON序列化

  • System.Text.Json
    is the default. Newtonsoft.Json only for legacy compatibility or features missing from STJ (e.g. polymorphic serialization in older runtimes).
  • Configure
    JsonSerializerOptions
    once and reuse - never construct per call.
  • Naming policy:
    JsonNamingPolicy.CamelCase
    for external APIs unless a contract requires otherwise.
  • Use source-generated
    JsonSerializerContext
    for hot paths and AOT compatibility.
  • Never deserialize untrusted JSON without size and depth limits.
  • 默认使用
    System.Text.Json
    。仅在遗留兼容性或STJ缺失特性(如旧运行时中的多态序列化)时使用Newtonsoft.Json。
  • 配置
    JsonSerializerOptions
    一次并复用——绝不每次调用都构造新实例。
  • 命名策略:外部API默认使用
    JsonNamingPolicy.CamelCase
    ,除非契约另有要求。
  • 热路径和AOT兼容性场景使用源代码生成的
    JsonSerializerContext
  • 反序列化不可信JSON时,必须设置大小和深度限制。

Decoupling and DI lifetimes

解耦与DI生命周期

  • Never call
    new
    on service-layer or infrastructure types inside a class body - use factories or DI.
  • No circular dependencies between namespaces.
  • Never inject a shorter-lifetime service into a longer-lifetime one (captive dependency). Use
    IServiceScopeFactory
    or a
    Func<T>
    factory for cross-lifetime access.
  • 类体内绝不直接实例化服务层或基础设施类型——使用工厂或DI。
  • 命名空间之间禁止循环依赖。
  • 绝不将短生命周期服务注入长生命周期服务(捕获依赖)。跨生命周期访问使用
    IServiceScopeFactory
    Func<T>
    工厂。