dotnet-conventions
Compare original and translation side by side
🇺🇸
Original
English🇨🇳
Translation
Chinese.NET Conventions
.NET 编码规范
Style & Formatting
风格与格式
- Follow rules strictly
.editorconfig - Run before committing
dotnet format - Minimize diffs: Change only what's necessary, preserve existing formatting and structure
- Match surrounding code style exactly
- 严格遵循规则
.editorconfig - 提交前运行
dotnet format - 最小化差异:只修改必要内容,保留现有格式和结构
- 完全匹配周边代码风格
Naming Conventions
命名规范
| Element | Convention | Example |
|---|---|---|
| Private fields | | |
| Public members | PascalCase | |
| Local variables | camelCase | |
| Constants | PascalCase | |
| Type parameters | | |
| 元素 | 规范 | 示例 |
|---|---|---|
| 私有字段 | | |
| 公共成员 | PascalCase | |
| 局部变量 | camelCase | |
| 常量 | PascalCase | |
| 类型参数 | | |
Formatting Rules
格式规则
- Indentation: 4 spaces, no tabs
- Namespaces: File-scoped ()
namespace Foo; - Usings: Outside namespace
- Braces: Always use, even for single-line blocks
- No : Never use
#region/#regiondirectives — they hide code and discourage refactoring#endregion
- 缩进:4个空格,不使用制表符
- 命名空间:文件范围的()
namespace Foo; - using指令:放在命名空间外部
- 大括号:始终使用,即使是单行代码块
- 禁止使用:永远不要使用
#region/#region指令——它们会隐藏代码,不利于重构#endregion
Async Patterns
异步模式
- Suffix: Always use suffix for async methods
Async - CancellationToken: Pass through call chains when available
- ValueTask: Prefer for hot paths that often complete synchronously
ValueTask<T> - 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)。