问题现象:AI生成的C#代码总是不对味
用Copilot或Cursor写过C#的朋友,大多遇到过这类情况:
- 类名用了小驼峰(
customerRepo),而.NET标准要求帕斯卡命名(CustomerRepo) - 异步方法没有
Async后缀,或者用了.Result阻塞 - 没有依赖注入意识,构造函数里
new一堆服务 - 日志不用
ILogger<T>,而是直接Console.WriteLine
AI不傻,只是它缺少一份 “当前上下文应该遵循什么规范” 的说明。默认情况下,大模型会按平均训练数据来生成——但C#的开源训练数据中风格非常杂,有Unity的、有Xamarin的、有古老WinForms的。而你要的是现代ASP.NET Core风格。
微软最近开源的 dotnet/skills 就是来解决这个问题的:提供一组预定义的“技能”文件,告诉AI代理特定的编码规则。今天我就拆解它的核心思路,并给你一个可以直接复用的模板。
上下文结构分析:Skill 文件里到底写了什么
我翻看了仓库里的几个Skill文件(例如 csharp-conventions.md),其设计模式非常清晰:
- 角色定义:
You are a senior .NET developer - 全局规则:明确的命名、异步、DI、日志规范
- 负面清单:
Do NOT use .Result. - 示例偏好:
Prefer switch expressions over if-else when pattern matching
这种结构的本质是 压缩上下文:把分散在数千篇文档中的最佳实践浓缩成半页规则。模型读一次就能记住并执行。

左:无Skill生成的代码,右:使用Skill后的代码。注意命名、异步标注、日志注入的差异。
优化方案:一份可直接复用的C#编码规范Prompt
下面是我从dotnet/skills中提取并优化的模板,你可以直接粘贴到GitHub Copilot的Custom Instructions或Cursor的Rules中。
You are a senior .NET developer, generating C# code for modern .NET 8+ projects.
Follow these rules strictly:
1. Naming:
- PascalCase for class names, public methods, properties, events, constants, and record names.
- camelCase for local variables, method parameters, and private fields (no underscore prefix unless for backing fields).
- Use `_camelCase` for private fields only when they are backing fields of auto-properties.
2. Async:
- Every async method must end with `Async` suffix.
- Use `await` instead of `.Result` or `.Wait()`. Never use `Task.WaitAll` or `Task.WhenAny` in application code.
- Expose `CancellationToken` parameter in all long-running operations.
3. Dependency Injection:
- Register services via constructor injection. Do NOT use `new` to instantiate services that have dependencies.
- Use `ILogger<T>` for logging. Do NOT use `Console.WriteLine` or `Debug.WriteLine`. Prefer structured logging.
4. Nullable Reference Types:
- Enable `<Nullable>enable</Nullable>`.
- Use `string?` for nullable strings, `string` for non-nullable.
- Avoid `!.` (null-forgiving operator) unless you are 100% sure.
5. Collections & Interfaces:
- Accept `IEnumerable<T>` for read-only input, `IReadOnlyCollection<T>` if count matters, `IList<T>` if indexed access is needed.
- Return `IReadOnlyList<T>` or `IEnumerable<T>` from public methods; avoid exposing `List<T>` or arrays directly.
6. Exceptions:
- Do NOT catch generic `Exception` unless you rethrow or log and recover in a specific way.
- Prefer custom exception classes derived from `Exception` for domain errors.
7. Code Style:
- Use `var` only when the type is obvious (e.g., `var list = new List<int>()`). Otherwise write explicit types.
- Prefer primary constructors for simple DTOs/records.
- Use file-scoped namespaces (`namespace Foo.Bar;`).
为什么这样写有效?
大模型的注意力会分散到上下文的每个角落。如果你只在对话中偶尔说一句“用PascalCase”,模型可能在关键决策时忘记。但如果你把规则列表放在靠前的位置(作为系统提示),模型会把它当作“任务说明书”,每次生成时都会参考。规则越具体、越像代码格式,模型执行准确性越高。
实验对比:差Prompt vs 好Prompt
我让同一个模型(GPT-4o)分别用两种Prompt生成同一个功能:一个异步日志记录类,供控制器使用。
差Prompt: 写一个C#日志记录器,在程序启动时写一条消息
public class Logger {
public void Log(string message) {
Console.WriteLine(message);
}
}
问题:同步、无DI、无命名规范、无结构化。
好Prompt(带上上面的Skill模板): 生成结果:
public sealed class LoggingService : ILoggingService
{
private readonly ILogger<LoggingService> _logger;
public LoggingService(ILogger<LoggingService> logger)
{
_logger = logger ?? throw new ArgumentNullException(nameof(logger));
}
public async Task LogStartupMessageAsync(string message, CancellationToken cancellationToken = default)
{
_logger.LogInformation("Application started: {Message}", message);
await Task.CompletedTask; // Placeholder for any async work
}
}
明显差异:异步后缀、DI注入、ILogger、CancellationToken、命名合规。

两个Prompt生成的代码在命名、异步、DI上的评分对比。好Prompt在每项上都达到满分。
适用场景和边界
这个模板适合以下场景:
- 编写新的API、服务、控制器、DTO、仓储层
- 团队希望统一代码风格,但还没时间写StyleCop规则
- 使用AI编码助手进行初期骨架生成
边界:
- 规则过多(超过20条)会导致模型忽略部分规则,建议精简到10条以内
- 对于底层性能敏感代码(如Span
、内存池),这些规则可能需要放宽(例如允许使用unsafe) - 旧项目(.NET Framework 4.x)不适用异步后缀等规则,需调整
扩展用法:2-3个变体
- EF Core Entity Skill:在规则中加入
Use data annotations only when necessary; prefer Fluent API in OnModelCreating、Add a private constructor for DbContext in migrations等。 - ASP.NET Core Minimal API Skill:强调
UseMapGetnotapp.UseEndpoints; Add[AsParameters]for complex queries。 - Blazor Component Skill:
Use@codenot code-behind; Use[Parameter]and[SupplyParameterFromQuery]; AvoidStateHasChangedin loops。
你完全可以基于这个模式,为你团队的项目定制一份Skill文件。dotnet/skills 最大的价值不是那几百个文件,而是它教了我们一种 “如何让AI理解你的代码规范” 的方法。
个人观点
我特别推荐你把这份Prompt放在C#项目的 .github/copilot-instructions.md 里(GitHub Copilot会读取),或者Cursor的 .cursorrules 中。这样整个团队在使用AI时,生成的代码会自动对齐到同一套标准,减少代码审查时因风格差异引发的无效讨论。
如果你想要更弹性的控制,还可以在规则清单最后加一句:If the code is for a legacy project, ask me before applying modern conventions. —— 这样AI会在关键决策前主动征求意见,而不是盲目执行。
这才是dotnet/skills背后真正的工程师思维:不是等着AI完美,而是用结构化上下文主动塑造AI的行为。