ARTICLE DETAIL

资讯详情

深耕网站建设、视觉设计与SEO优化的一线实战洞察。

C 代码规范实战指南:Conductor 模板驱动的 .NET 编码约定与最佳实践

C 代码规范实战指南:Conductor 模板驱动的 .NET 编码约定与最佳实践 C# 代码规范实战指南Conductor 模板驱动的 .NET 编码约定与最佳实践【免费下载链接】agentsMulti-harness agentic plugin marketplace for Claude Code, Codex, Cursor, OpenCode, GitHub Copilot, and Google Antigravity项目地址: https://gitcode.com/GitHub_Trending/agents24/agentsConductorContext-Driven Development 插件在/conductor:setup初始化项目时会基于templates/code_styleguides/下的模板为选定语言生成项目级编码规范其中csharp.md正是面向 .NET/C# 的标准风格指南。本篇以该模板为骨架逐节讲解 C# 的命名约定、异步编程、LINQ、依赖注入、单元测试与代码组织规范并辅以仓库中 dotnet-contribution 插件README.md的源码资产交叉印证帮助你在自己的 .NET 项目中快速落地一套既符合社区惯例、又可持续约束 AI 协作产出的编码基线。该规范在 Conductor 项目中的角色在 Conductor 生成的每个项目上下文里语言级编码规范都保存在conductor/code_styleguides/目录下。根据 setup.md 的描述交互式初始化Section 5Code Style Guides会先询问“要为哪些语言生成风格指南”如 TypeScript/JavaScript、Python、Go、Rust、全部检测到的语言或跳过并询问是否引入已有 lint/format 配置随后在 Artifact Generation 阶段从$CLAUDE_PLUGIN_ROOT/templates/code_styleguides/复制对应语言模板到目标项目。因此本模板实际承担两个角色作为「项目级规范模板」被 conductor/templates/index.md 的 Style Guides 导航表登记为 “C# conventions”与 general.md通用原则、typescript、javascript、python、go、dart、html-css 等并行作为「可执行规范清单」在后续/conductor:new-track、/conductor:implement的开发流程中约束生成的 C# 代码风格。值得注意的是模板中的约定并非孤立主张。仓库内 dotnet-contribution 插件的真实 C# 资产如 repository-template.cs、service-template.cs在接口命名、异步方法后缀、下划线私有字段、构造函数注入、日志与空值处理等方面与本模板高度一致说明这些约定有可对照的实际工程样板。命名约定Naming Conventions通用规则C# 命名首要遵循每种作用域使用唯一大小写风格的原则让阅读者仅凭大小写即可判断标识符的用途// PascalCase for public members, types, namespaces public class UserService { } public void ProcessOrder() { } public string FirstName { get; set; } // camelCase for private fields, parameters, locals private readonly ILogger _logger; private int _itemCount; public void DoWork(string inputValue) { } // Prefix interfaces with I public interface IUserRepository { } public interface INotificationService { } // Suffix async methods with Async public async TaskUser GetUserAsync(int id) { } public async Task ProcessOrderAsync(Order order) { } // Constants: PascalCase (not SCREAMING_CASE) public const int MaxRetryCount 3; public const string DefaultCurrency USD;逐条拆解PascalCase用于公共成员、类型与命名空间camelCase用于私有字段可叠加下划线前缀见下文、方法参数与局部变量接口前缀 I是 .NET 框架惯例不应省略异步方法以Async结尾。在 repository-template.cs 中可以看到该约定被严格执行——GetByIdAsync、SearchAsync、CreateAsync、UpdateAsync、DeleteAsync全部遵循此命名常量用 PascalCase不用SCREAMING_CASE这是 .NET 命名指南与多数 C/C/Python 习惯的关键差异跨语言协作时尤其容易踩坑。字段与属性命名类型内部需要区分「可变状态存储」与「对外暴露契约」public class Order { // Private fields: underscore prefix camelCase private readonly IOrderRepository _repository; private int _itemCount; // Public properties: PascalCase public int Id { get; set; } public string CustomerName { get; set; } public DateTime CreatedAt { get; init; } // Boolean properties: Is/Has/Can prefix public bool IsActive { get; set; } public bool HasDiscount { get; set; } public bool CanEdit { get; } }私有字段使用_下划线前缀 camelCase_repository、_itemCountreadonly字段应显式标注公共属性使用 PascalCase布尔属性使用Is/Has/Can动词前缀让条件判断读起来接近自然语言只读语义用init访问器初始化后不可变或仅get。该规则在仓库 C# 资产中同样有印证EfCoreProductRepository与DapperProductRepository都以_context、_connection、_logger这类下划线 camelCase 私有字段存储注入依赖。异步/等待模式Async/Await Patterns基础用法C# 中所有 I/O 型操作都应优先采用async/await而不是同步阻塞或“伪异步”写法// Always use async/await for I/O operations public async TaskUser GetUserAsync(int id) { var user await _repository.FindAsync(id); if (user null) { throw new NotFoundException($User {id} not found); } return user; } // Dont block on async code // Bad var user GetUserAsync(id).Result; // Good var user await GetUserAsync(id);反模式.Result、.Wait()同步阻塞异步任务会引发线程池饥饿尤其在高并发 Web 场景并可能造成死锁在带有 SynchronizationContext 的环境中尤甚。任何同步阻塞替代方案都应被认定为需要整改的坏味道。异步最佳实践库代码中使用ConfigureAwait(false)避免向调用方上下文回投递从而降低死锁风险并提升吞吐public async TaskData FetchDataAsync() { var response await _httpClient.GetAsync(url) .ConfigureAwait(false); return await response.Content.ReadAsAsyncData() .ConfigureAwait(false); }避免async void其异常无法被调用方捕获会直接传播到线程池或应用崩溃。除 UI/框架事件处理器外一律返回Task// Bad public async void ProcessOrder() { } // Good public async Task ProcessOrderAsync() { } // Event handler exception private async void Button_Click(object sender, EventArgs e) { try { await ProcessOrderAsync(); } catch (Exception ex) { HandleError(ex); } }事件处理器若必须使用async void则必须在方法体内用try/catch包裹全部逻辑防止未观察异常击穿应用。并行异步操作独立操作应并行执行用Task.WhenAll汇聚结果响应式并发控制用SemaphoreSlim做节流// Execute independent operations in parallel public async TaskDashboardData LoadDashboardAsync() { var usersTask _userService.GetActiveUsersAsync(); var ordersTask _orderService.GetRecentOrdersAsync(); var statsTask _statsService.GetDailyStatsAsync(); await Task.WhenAll(usersTask, ordersTask, statsTask); return new DashboardData { Users await usersTask, Orders await ordersTask, Stats await statsTask }; } // Use SemaphoreSlim for throttling public async Task ProcessItemsAsync(IEnumerableItem items) { using var semaphore new SemaphoreSlim(10); // Max 10 concurrent var tasks items.Select(async item { await semaphore.WaitAsync(); try { await ProcessItemAsync(item); } finally { semaphore.Release(); } }); await Task.WhenAll(tasks); }要点先启动全部 Task再统一WhenAll避免串行等待放大延迟await之后再次读取await tasksTask获取结果值SemaphoreSlim并发数示例为 10需按下游限流能力调整Release()放在finally中确保异常路径也能归还信号量传入CancellationToken如 repository-template.cs 中每个方法末尾的CancellationToken ct default是生产代码的另一项硬要求便于优雅取消。LINQ查询语法与方法语法简单查询优先方法语法链式、可读性强含 join / group 的复杂查询可用查询语法表达意图更紧凑// Method syntax (preferred for simple queries) var activeUsers users .Where(u u.IsActive) .OrderBy(u u.Name) .ToList(); // Query syntax (for complex queries with joins) var orderSummary from order in orders join customer in customers on order.CustomerId equals customer.Id where order.Total 100 group order by customer.Name into g select new { Customer g.Key, Total g.Sum(o o.Total) };LINQ 最佳实践用语义匹配的方法而不是“凑出来”的等价物var hasItems items.Any(); // Not: items.Count() 0 var firstOrDefault items.FirstOrDefault(); // Not: items.First() var count items.Count; // Property, not Count()Count()对已实现ICollection的序列虽已优化但对IEnumerable需要完整遍历First()在无元素时抛异常而FirstOrDefault()返回default。判断“是否存在元素”务必用Any()可提前短路而非Count() 0对ListT等类型直接取.Count属性而非Count()扩展方法。避免对IEnumerable的多次枚举——每次枚举都可能重新执行底层查询数据库往返或重复计算// Bad if (items.Any()) { foreach (var item in items) { } } // Good var itemList items.ToList(); if (itemList.Count 0) { foreach (var item in itemList) { } }尽早投影、只取需要的列减少内存占用与传输量在 EF Core / Dapper 场景下对应“只 SELECT 必要列”var names users .Where(u u.IsActive) .Select(u u.Name) // Select only what you need .ToList();常用 LINQ 操作速查// Filtering var adults people.Where(p p.Age 18); // Transformation var names people.Select(p ${p.FirstName} {p.LastName}); // Aggregation var total orders.Sum(o o.Amount); var average scores.Average(); var max values.Max(); // Grouping var byDepartment employees .GroupBy(e e.Department) .Select(g new { Department g.Key, Count g.Count() }); // Joining var result orders .Join(customers, o o.CustomerId, c c.Id, (o, c) new { Order o, Customer c }); // Flattening var allOrders customers.SelectMany(c c.Orders);注意Sum(o o.Amount)对decimal?等可空类型返回可空结果使用前需留意SelectMany用于“把集合的集合拍平”是避免嵌套循环遍历的首选表达。依赖注入Dependency Injection服务注册与生命周期ASP.NET Core 中所有协作依赖都应在Program.cs或Startup.ConfigureServices集中注册并按真实生命周期选择容器行为public void ConfigureServices(IServiceCollection services) { // Transient: new instance each time services.AddTransientIEmailService, EmailService(); // Scoped: one instance per request services.AddScopedIUserRepository, UserRepository(); // Singleton: one instance for app lifetime services.AddSingletonICacheService, MemoryCacheService(); // Factory registration services.AddScopedIDbConnection(sp { var config sp.GetRequiredServiceIConfiguration(); return new SqlConnection(config.GetConnectionString(Default)); }); }三类生命周期的选择要点Transient每次解析都是新实例适合无状态轻量服务Scoped每个请求/作用域一个实例是DbContext、仓储类的常见归属Singleton进程级单例适合无状态缓存、配置类服务绝不可把 Scoped/Singleton 依赖反向注入到更长生命周期的服务中会形成“捕获依赖”陷阱工厂注册当类型构造需要运行时参数如按配置动态构造SqlConnection时通过解析器回调完成装配。构造器注入依赖通过构造函数显式声明字段全部readonly并用空值守卫在入口即失败public class OrderService : IOrderService { private readonly IOrderRepository _repository; private readonly ILoggerOrderService _logger; private readonly IEmailService _emailService; public OrderService( IOrderRepository repository, ILoggerOrderService logger, IEmailService emailService) { _repository repository ?? throw new ArgumentNullException(nameof(repository)); _logger logger ?? throw new ArgumentNullException(nameof(logger)); _emailService emailService ?? throw new ArgumentNullException(nameof(emailService)); } public async TaskOrder CreateOrderAsync(OrderRequest request) { _logger.LogInformation(Creating order for customer {CustomerId}, request.CustomerId); var order new Order(request); await _repository.SaveAsync(order); await _emailService.SendOrderConfirmationAsync(order); return order; } }实践要点每个依赖一个构造函数参数直接赋值给readonly字段构造器内用?? throw new ArgumentNullException(nameof(...))做守卫保证对象在不可用状态下根本不会被创建使用结构化日志占位符{CustomerId}而非字符串拼接便于日志系统索引检索。这一“多参数构造器 下划线 readonly 字段 空值守卫”的结构正是 repository-template.cs 中DapperProductRepository与EfCoreProductRepository两个真实实现的标准形态。Options 模式强类型配置优先使用IOptionsT而非到处读IConfiguration字符串键// Configuration class public class EmailSettings { public string SmtpServer { get; set; } public int Port { get; set; } public string FromAddress { get; set; } } // Registration services.ConfigureEmailSettings( configuration.GetSection(Email)); // Usage public class EmailService { private readonly EmailSettings _settings; public EmailService(IOptionsEmailSettings options) { _settings options.Value; } }对应 appsettings.json 中同名 sectionEmail: { SmtpServer: ..., Port: ..., FromAddress: ... }即可完成绑定配合[Required]、[Range]等数据注解做启动期校验是生产级配置的推荐做法。测试TestingxUnit 基础Fact 与 Theory[Fact]表示单条确定性测试[Theory][InlineData]用多组数据复用同一测试逻辑方法命名采用方法_场景_期望结果Add_TwoPositiveNumbers_ReturnsSum与 general 风格指南中 “Describe behavior, not implementation” 一脉相承public class CalculatorTests { [Fact] public void Add_TwoPositiveNumbers_ReturnsSum() { // Arrange var calculator new Calculator(); // Act var result calculator.Add(2, 3); // Assert Assert.Equal(5, result); } [Theory] [InlineData(1, 1, 2)] [InlineData(0, 0, 0)] [InlineData(-1, 1, 0)] public void Add_VariousNumbers_ReturnsCorrectSum(int a, int b, int expected) { var calculator new Calculator(); Assert.Equal(expected, calculator.Add(a, b)); } }用 Moq 做行为驱动 Mock单元测试只应验证被测服务自身的编排逻辑外部依赖用MockT替身public class OrderServiceTests { private readonly MockIOrderRepository _mockRepository; private readonly MockILoggerOrderService _mockLogger; private readonly OrderService _service; public OrderServiceTests() { _mockRepository new MockIOrderRepository(); _mockLogger new MockILoggerOrderService(); _service new OrderService(_mockRepository.Object, _mockLogger.Object); } [Fact] public async Task GetOrderAsync_ExistingOrder_ReturnsOrder() { // Arrange var expectedOrder new Order { Id 1, Total 100m }; _mockRepository .Setup(r r.FindAsync(1)) .ReturnsAsync(expectedOrder); // Act var result await _service.GetOrderAsync(1); // Assert Assert.Equal(expectedOrder.Id, result.Id); _mockRepository.Verify(r r.FindAsync(1), Times.Once); } [Fact] public async Task GetOrderAsync_NonExistingOrder_ThrowsNotFoundException() { // Arrange _mockRepository .Setup(r r.FindAsync(999)) .ReturnsAsync((Order)null); // Act Assert await Assert.ThrowsAsyncNotFoundException( () _service.GetOrderAsync(999)); } }核心手法构造器中准备好全部 mock 与被测实例_service减少每个用例的样板代码Setup(...).ReturnsAsync(...)定义行为Verify(..., Times.Once)断言交互次数从而把“结果正确”和“调用关系正确”都纳入测试异步异常场景用Assert.ThrowsAsyncT断言同时ReturnsAsync((Order)null)模拟“查无此人”。集成测试WebApplicationFactory端到端验证 HTTP 契约状态码、Content-Type、路由时用WebApplicationFactoryProgram直接启动内存中的宿主public class ApiIntegrationTests : IClassFixtureWebApplicationFactoryProgram { private readonly HttpClient _client; public ApiIntegrationTests(WebApplicationFactoryProgram factory) { _client factory.CreateClient(); } [Fact] public async Task GetUsers_ReturnsSuccessAndCorrectContentType() { // Act var response await _client.GetAsync(/api/users); // Assert response.EnsureSuccessStatusCode(); Assert.Equal(application/json; charsetutf-8, response.Content.Headers.ContentType.ToString()); } }IClassFixtureWebApplicationFactoryProgram让整个测试类共享同一测试服务器CreateClient()返回可直接发请求的HttpClient如需替换真实外部依赖可覆写工厂的ConfigureWebHost/ConfigureTestServices注入 mock。这与 dotnet-contribution 中列出的测试技术栈xUnit Moq WebApplicationFactory完全对应。常见模式Common Patterns空值处理现代 C#启用可空引用类型后应组合使用空条件、空合并与模式匹配// Null-conditional operators var length customer?.Address?.Street?.Length; var name user?.Name ?? Unknown; // Null-coalescing assignment list ?? new ListItem(); // Pattern matching for null checks if (user is not null) { ProcessUser(user); } // Guard clauses public void ProcessOrder(Order order) { ArgumentNullException.ThrowIfNull(order); if (order.Items.Count 0) { throw new ArgumentException(Order must have items, nameof(order)); } // Process... }?.让链式成员访问在任一环节为空时安全短路??提供默认值??惰性初始化is not null是比! null更受推荐的空值模式匹配写法对重载了的类型也安全参数守卫优先用 .NET 6 的ArgumentNullException.ThrowIfNull(order)一行式 API。Record 与 Init-Only 属性不可变数据建模优先用record配合with表达式获得“复制并修改”语义// Record for immutable data public record User(int Id, string Name, string Email); // Record with additional members public record Order { public int Id { get; init; } public string CustomerName { get; init; } public decimal Total { get; init; } public bool IsHighValue Total 1000; } // Record mutation via with expression var updatedUser user with { Name New Name };record自动提供基于值的相等性比较与ToString天然适合 DTO、领域值对象init属性在对象初始化后不再可变共同构建“一经创建即不可变”的数据流便于并发安全与缓存。模式匹配switch 表达式用表达式形式的switch代替冗长if/else与强制转换// Type patterns public decimal CalculateDiscount(object customer) customer switch { PremiumCustomer p p.PurchaseTotal * 0.2m, RegularCustomer r when r.YearsActive 5 r.PurchaseTotal * 0.1m, RegularCustomer r r.PurchaseTotal * 0.05m, null 0m, _ throw new ArgumentException(Unknown customer type) }; // Property patterns public string GetShippingOption(Order order) order switch { { Total: 100, IsPriority: true } Express, { Total: 100 } Standard, { IsPriority: true } Priority, _ Economy }; // List patterns (C# 11) public bool IsValidSequence(int[] numbers) numbers switch { [1, 2, 3] true, [1, .., 3] true, [_, _, ..] numbers.Length 2, _ false };类型模式PremiumCustomer p、RegularCustomer r when ...依次匹配并解包when提供额外条件属性模式{ Total: 100, IsPriority: true }直接对属性值做关系与布尔匹配代码密集且易读列表模式C# 11[1, .., 3]中的..slice pattern匹配任意长度的中间段适合数组/序列的结构化校验。Disposable 模式非托管资源必须实现IDisposable使用处用using或using var范围式声明保证及时释放public class ResourceManager : IDisposable { private bool _disposed; private readonly FileStream _stream; public ResourceManager(string path) { _stream File.OpenRead(path); } public void DoWork() { ObjectDisposedException.ThrowIf(_disposed, this); // Work with _stream } public void Dispose() { Dispose(true); GC.SuppressFinalize(this); } protected virtual void Dispose(bool disposing) { if (_disposed) return; if (disposing) { _stream?.Dispose(); } _disposed true; } } // Using statement using var manager new ResourceManager(file.txt); manager.DoWork();规范要点公开的Dispose()调用受保护的Dispose(bool)虚方法供派生类扩展清理逻辑置_disposed true并调用GC.SuppressFinalize(this)避免对象被不必要地送入终结队列使用方始终通过using var声明方法作用域结束时自动调用Dispose()任何已释放对象上的操作由ObjectDisposedException.ThrowIf兜底拦截。代码组织Code Organization文件内成员排列“一类型一文件”是通用原则单个类型内部成员按固定优先级从上到下排列形成稳定的阅读顺序public class UserService { // 1. Constants private const int MaxRetries 3; // 2. Static fields private static readonly object _lock new(); // 3. Instance fields private readonly IUserRepository _repository; // 4. Constructors public UserService(IUserRepository repository) { _repository repository; } // 5. Properties public int TotalUsers { get; private set; } // 6. Public methods public async TaskUser GetUserAsync(int id) { } // 7. Private methods private void ValidateUser(User user) { } }说明文件末尾以#region分组多实现时如 repository-template.cs 中按 “Dapper Implementation / EF Core Implementation / DbContext Configuration / Advanced Patterns / Entity Definitions” 组织同样遵循“从外部契约到内部细节”的顺序思想。解决方案级项目结构跨项目的分层结构建议按职责拆分 src/tests避免循环依赖与“万能类库”Solution/ ├── src/ │ ├── MyApp.Api/ # Web API project │ ├── MyApp.Core/ # Domain/business logic │ ├── MyApp.Infrastructure/ # Data access, external services │ └── MyApp.Shared/ # Shared utilities ├── tests/ │ ├── MyApp.UnitTests/ │ └── MyApp.IntegrationTests/ └── MyApp.sln这套布局与前述各层约定协同工作MyApp.Api只承载控制器/中间件/DI 装配不包含业务规则MyApp.Core领域实体、领域服务与接口定义对I前缀接口 record 模式匹配的天然归宿MyApp.InfrastructureEF Core/Dapper 实现与外部服务适配器Async后缀与ConfigureAwait(false)的高频区MyApp.Shared跨层工具保持低耦合、高内聚tests与src平行单元测试与集成测试WebApplicationFactory目录隔离构建产物互不污染。结合 Conductor 工作流落地本规范要把以上规范真正变成团队的协作基线推荐路径如下运行/conductor:setup见 setup.md在 Code Style Guides 问答中选择生成 C# 指南生成的conductor/code_styleguides/csharp.md即本模板的副本在 index.md 的导航表中确认 C# 指南被登记方便 AI 与成员随时查阅建立新 track/conductor:new-track并在spec.md中声明“遵循code_styleguides/csharp.md”随后/conductor:implement阶段的生成代码即会被这些约定持续约束与 dotnet-contribution 插件配合使用其仓库类模板、服务类模板与 EF Core/Dapper 参考文档可作为规范的可运行范例让抽象约定落到可复制代码针对团队已有代码还可参考 general.md 中 “Code Review Checklist” 的检查项可读性、边界、错误处理、安全、测试覆盖、约定一致性逐项验收。小结本 C# 风格指南覆盖了从标识符命名、异步并发、LINQ 到依赖注入、测试与工程结构的一整套 .NET 开发约定。它既是 Conductor 初始化流程生成的“项目宪法”也可独立作为团队 C# 编码评审的核对清单。实际使用时建议命名即文档让 PascalCase/camelCase/I前缀/Async后缀自动传达代码意图async 全链路化绝不.Result/.Wait()库代码ConfigureAwait(false)事件处理器例外必须 try/catchLINQ 语义优先、避免重复枚举、尽早投影DI 按生命周期选型、构造器注入 空值守卫、IOptionsT管理配置用 xUnit Moq WebApplicationFactory建立“单元到契约”的测试纵深代码组织遵循“一类型一文件、成员有序、src/tests 分层”的工程化结构。将本模板复制进你的项目conductor/code_styleguides/即可让 AI 编码助手与人类开发者共用同一套 .NET 语言规范从源头减少评审分歧与返工。【免费下载链接】agentsMulti-harness agentic plugin marketplace for Claude Code, Codex, Cursor, OpenCode, GitHub Copilot, and Google Antigravity项目地址: https://gitcode.com/GitHub_Trending/agents24/agents创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
返回列表