dotnet-conventions

Compare original and translation side by side

🇺🇸

Original

English
🇨🇳

Translation

Chinese

.NET Conventions

.NET 编码规范

Style & Formatting

风格与格式

  • Follow
    .editorconfig
    rules strictly
  • Run
    dotnet format
    before committing
  • Minimize diffs: Change only what's necessary, preserve existing formatting and structure
  • Match surrounding code style exactly
  • 严格遵循
    .editorconfig
    规则
  • 提交前运行
    dotnet format
  • 最小化差异:只修改必要内容,保留现有格式和结构
  • 完全匹配周边代码风格

Naming Conventions

命名规范

ElementConventionExample
Private fields
_camelCase
_organizationRepository
Public membersPascalCase
GetByIdAsync
Local variablescamelCase
organizationId
ConstantsPascalCase
MaxRetryCount
Type parameters
T
prefix
TModel
元素规范示例
私有字段
_camelCase
_organizationRepository
公共成员PascalCase
GetByIdAsync
局部变量camelCase
organizationId
常量PascalCase
MaxRetryCount
类型参数
T
前缀
TModel

Formatting Rules

格式规则

  • Indentation: 4 spaces, no tabs
  • Namespaces: File-scoped (
    namespace Foo;
    )
  • Usings: Outside namespace
  • Braces: Always use, even for single-line blocks
  • No
    #region
    : Never use
    #region
    /
    #endregion
    directives — they hide code and discourage refactoring
  • 缩进:4个空格,不使用制表符
  • 命名空间:文件范围的(
    namespace Foo;
  • using指令:放在命名空间外部
  • 大括号:始终使用,即使是单行代码块
  • 禁止使用
    #region
    :永远不要使用
    #region
    /
    #endregion
    指令——它们会隐藏代码,不利于重构

Async Patterns

异步模式

  • Suffix: Always use
    Async
    suffix for async methods
  • CancellationToken: Pass through call chains when available
  • ValueTask: Prefer
    ValueTask<T>
    for hot paths that often complete synchronously
  • ConfigureAwait: Not required in ASP.NET Core
csharp
// From src/Exceptionless.Core/Services/UsageService.cs
public async Task SavePendingUsageAsync()
{
    var utcNow = _timeProvider.GetUtcNow().UtcDateTime;
    await SavePendingOrganizationUsageAsync(utcNow);
    await SavePendingProjectUsageAsync(utcNow);
}
  • 后缀:异步方法必须添加
    Async
    后缀
  • CancellationToken:在可用时传递调用链
  • ValueTask:对于经常同步完成的热路径,优先使用
    ValueTask<T>
  • ConfigureAwait:在ASP.NET Core中不需要
csharp
// From src/Exceptionless.Core/Services/UsageService.cs
public async Task SavePendingUsageAsync()
{
    var utcNow = _timeProvider.GetUtcNow().UtcDateTime;
    await SavePendingOrganizationUsageAsync(utcNow);
    await SavePendingProjectUsageAsync(utcNow);
}

Structured Logging

结构化日志

Use message templates with named placeholders — values go in the args, not string interpolation:
csharp
// ✅ Correct: Named placeholders for structured data
_logger.LogInformation("Saving org ({OrganizationId}-{OrganizationName}) event usage",
    organizationId, organization.Name);

_logger.LogError(ex, "Error retrieving event post payload: {Path}", path);

_logger.LogWarning("Unable to parse user agent {UserAgent}. Exception: {Message}",
    userAgent, ex.Message);

// ❌ Wrong: String interpolation loses structure
_logger.LogInformation($"Saving org {organizationId}");
使用带命名占位符的消息模板——参数值放在args中,不要用字符串插值:
csharp
// ✅ Correct: Named placeholders for structured data
_logger.LogInformation("Saving org ({OrganizationId}-{OrganizationName}) event usage",
    organizationId, organization.Name);

_logger.LogError(ex, "Error retrieving event post payload: {Path}", path);

_logger.LogWarning("Unable to parse user agent {UserAgent}. Exception: {Message}",
    userAgent, ex.Message);

// ❌ Wrong: String interpolation loses structure
_logger.LogInformation($"Saving org {organizationId}");

Log Scopes with ExceptionlessState

使用ExceptionlessState的日志作用域

Use scopes to add context to all log entries within a block:
csharp
// From src/Exceptionless.Core/Jobs/EventPostsJob.cs
using var _ = _logger.BeginScope(new ExceptionlessState()
    .Organization(ep.OrganizationId)
    .Project(ep.ProjectId));

// All log entries in this scope automatically include org/project context
_logger.LogInformation("Processing event post");
Add tags and properties for richer context:
csharp
using (_logger.BeginScope(new ExceptionlessState()
    .Organization(organization.Id)
    .Tag("Delete")
    .Tag("Bot")))
{
    _logger.LogInformation("Removing bot events");
}
使用作用域为代码块内的所有日志条目添加上下文:
csharp
// From src/Exceptionless.Core/Jobs/EventPostsJob.cs
using var _ = _logger.BeginScope(new ExceptionlessState()
    .Organization(ep.OrganizationId)
    .Project(ep.ProjectId));

// All log entries in this scope automatically include org/project context
_logger.LogInformation("Processing event post");
添加标签和属性以获得更丰富的上下文:
csharp
using (_logger.BeginScope(new ExceptionlessState()
    .Organization(organization.Id)
    .Tag("Delete")
    .Tag("Bot")))
{
    _logger.LogInformation("Removing bot events");
}

Nullable Reference Types

可为空引用类型

  • Honor nullable annotations throughout
  • Treat nullable warnings as errors
  • Use
    ?
    suffix for nullable types
csharp
public async Task<User?> FindUserAsync(string? email)
{
    if (string.IsNullOrWhiteSpace(email))
        return null;

    return await _repository.FindByEmailAsync(email);
}
  • 全程遵循可为空注解
  • 将可为空警告视为错误
  • 可为空类型使用
    ?
    后缀
csharp
public async Task<User?> FindUserAsync(string? email)
{
    if (string.IsNullOrWhiteSpace(email))
        return null;

    return await _repository.FindByEmailAsync(email);
}

Resource Disposal

资源释放

csharp
// Prefer using declarations
using var stream = File.OpenRead(path);

// Async disposal
await using var connection = await CreateConnectionAsync();
csharp
// Prefer using declarations
using var stream = File.OpenRead(path);

// Async disposal
await using var connection = await CreateConnectionAsync();

Constructor Injection

构造函数注入

Prefer constructor injection with readonly fields:
csharp
// From src/Exceptionless.Core/Services/UsageService.cs
public class UsageService
{
    private readonly IOrganizationRepository _organizationRepository;
    private readonly ICacheClient _cache;
    private readonly TimeProvider _timeProvider;
    private readonly ILogger _logger;

    public UsageService(
        IOrganizationRepository organizationRepository,
        ICacheClient cache,
        TimeProvider timeProvider,
        ILoggerFactory loggerFactory)
    {
        _organizationRepository = organizationRepository;
        _cache = cache;
        _timeProvider = timeProvider;
        _logger = loggerFactory.CreateLogger<UsageService>();
    }
}
优先使用带只读字段的构造函数注入:
csharp
// From src/Exceptionless.Core/Services/UsageService.cs
public class UsageService
{
    private readonly IOrganizationRepository _organizationRepository;
    private readonly ICacheClient _cache;
    private readonly TimeProvider _timeProvider;
    private readonly ILogger _logger;

    public UsageService(
        IOrganizationRepository organizationRepository,
        ICacheClient cache,
        TimeProvider timeProvider,
        ILoggerFactory loggerFactory)
    {
        _organizationRepository = organizationRepository;
        _cache = cache;
        _timeProvider = timeProvider;
        _logger = loggerFactory.CreateLogger<UsageService>();
    }
}

Validation Patterns

验证模式

Input Validation

输入验证

Validate early, fail fast:
csharp
public async Task<ActionResult> ProcessAsync(string id)
{
    if (string.IsNullOrEmpty(id))
        return BadRequest("Id is required");

    var entity = await _repository.GetByIdAsync(id);
    if (entity is null)
        return NotFound();

    // Continue processing
}
尽早验证,快速失败:
csharp
public async Task<ActionResult> ProcessAsync(string id)
{
    if (string.IsNullOrEmpty(id))
        return BadRequest("Id is required");

    var entity = await _repository.GetByIdAsync(id);
    if (entity is null)
        return NotFound();

    // Continue processing
}

Domain Validation

领域验证

See backend-architecture for validation patterns (FluentValidation for domain models, MiniValidator for API requests).
有关验证模式,请参阅backend-architecture(领域模型使用FluentValidation,API请求使用MiniValidator)。