用 dotnet/skills 让 AI 写 C# 代码更靠谱

微软昨天(2025年4月)在 GitHub 上开源了一个叫 dotnet/skills 的项目,一天内涨了 2458 个 star。这玩意儿不是新框架,也不是新语言,而是一套给 AI 编码助手准备的提示词模板——专门针对 .NET 和 C# 场景。

说白了,就是告诉 Copilot、Cursor 这类 AI 工具:"怎么写 C# 代码才符合微软官方的最佳实践"。

这个项目解决什么问题?

用过 AI 写 C# 代码的开发者应该都有同感:

  • AI 经常生成过时的语法(比如还在用 ArrayList 而不是 List<T>
  • 异步编程时,AI 会忘记加 await 或者用 Task.Wait()(死锁警告)
  • 依赖注入的写法不规范,AI 不知道用 AddScoped 还是 AddTransient
  • 生成的代码不遵循 .NET 命名规范(方法名大写开头?AI 有时会写成小写)

dotnet/skills 就是给 AI 打补丁——通过精心设计的提示词,告诉 AI 在生成 C# 代码时要遵循什么规则。

核心思路:用规则约束 AI

项目的核心文件是 .github/skills.md(在 GitHub 仓库里),里面写了 AI 在回答 C# 问题时应遵守的规则。比如:

当用户询问 C# 代码时,请遵循 .NET 编码指南,使用最新的语言特性(如记录类型、模式匹配),优先使用异步编程模型,避免使用已弃用的 API。

这不是什么玄学,就是最朴素的提示词工程——把微软官方的编码规范翻译成 AI 能理解的指令。

我看了下仓库里的内容,主要覆盖了这几个方面:

  1. 语法规范:用 C# 12 的新特性(主构造函数、集合表达式等)
  2. 异步模式:必须用 await,不用 Task.ResultTask.Wait()
  3. 依赖注入:正确使用生命周期(AddScoped vs AddTransient vs AddSingleton
  4. 异常处理:用 Result 模式而不是裸抛异常
  5. 性能指南:避免装箱、用 Span<T> 处理内存等

完整提示词模板(可直接复制)

我根据项目内容整理了一个可以直接用的提示词模板,你把它放到 AI 工具的系统提示词里就行:

markdown
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31
你是一个 C# 代码生成助手。请严格遵循以下规则:

1. 使用 C# 12 或更高版本语法,优先使用:
   - 主构造函数(primary constructors)
   - 集合表达式(collection expressions)
   - 模式匹配(pattern matching)
   - 记录类型(record types)

2. 异步编程必须:
   - 所有 I/O 操作使用 `await`
   - 方法名以 `Async` 结尾
   - 避免使用 `Task.Result`、`Task.Wait()`、`Task.Run()`(除非明确需要)

3. 依赖注入:
   - 无状态服务用 `AddSingleton`
   - 有状态服务用 `AddScoped`(Web 应用默认)
   - 轻量级临时服务用 `AddTransient`

4. 命名规范:
   - 接口以 `I` 开头
   - 方法名 PascalCase
   - 私有字段 `_camelCase`

5. 错误处理:
   - 优先使用 `Result` 模式(如 `OneOf`、`FluentResults`)而不是裸抛异常
   - 只在不可恢复的错误时抛异常

6. 性能:
   - 避免装箱(boxing)
   - 处理大文件用 `Stream` 而不是 `ReadAllText`
   - 复杂字符串拼接用 `StringBuilder`

效果演示:差 Prompt vs 好 Prompt

我拿一个常见场景测试:让 AI 写一个读取文件并处理内容的服务。

❌ 差 Prompt(不给任何规则)

写一个 C# 类,从文件读取 JSON 数据并返回列表。

AI 输出(典型的 "能用但不好" 的代码):

csharp
1 2 3 4 5 6 7 8 9
public class DataService
{
    public List<Item> GetData(string filePath)
    {
        var json = File.ReadAllText(filePath);
        var items = JsonConvert.DeserializeObject<List<Item>>(json);
        return items;
    }
}

问题:

  • File.ReadAllText 阻塞线程(大文件时会卡 UI)
  • 返回 List<Item> 而不是 IReadOnlyList<Item>(违反封装原则)
  • JsonConvert(Newtonsoft.Json)而不是 System.Text.Json(微软官方推荐)
  • 没有异常处理
  • 方法名不是异步模式

✅ 好 Prompt(用上面的规则模板)

同样要求,但加上规则后:

csharp
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
public sealed class DataService(ILogger<DataService> logger)
{
    public async Task<IReadOnlyList<Item>> GetDataAsync(string filePath, CancellationToken cancellationToken = default)
    {
        try
        {
            await using var stream = new FileStream(filePath, FileMode.Open, FileAccess.Read, FileShare.Read, 4096, useAsync: true);
            var items = await JsonSerializer.DeserializeAsync<List<Item>>(stream, cancellationToken: cancellationToken);
            return items?.AsReadOnly() ?? [];
        }
        catch (FileNotFoundException ex)
        {
            logger.LogError(ex, "File not found: {FilePath}", filePath);
            throw; // 不可恢复的错误
        }
        catch (JsonException ex)
        {
            logger.LogError(ex, "Invalid JSON in file: {FilePath}", filePath);
            return []; // 可恢复的错误,返回空列表
        }
    }
}

对比结果:

维度 差 Prompt 好 Prompt
异步 同步阻塞 异步流式处理
序列化 Newtonsoft.Json System.Text.Json
异常处理 区分可恢复/不可恢复
性能 全量读入内存 流式读取
可测试性 低(硬编码依赖) 高(主构造函数注入日志)
取消支持 支持 CancellationToken

差距不是一点点——好 Prompt 生成的代码直接能上生产环境,差的那个只能算 Demo。

对比表格展示差Prompt和好Prompt的代码生成差异

为什么这样写有效?

这不是玄学,背后有明确的原理:

  1. AI 模型有 "上下文遗忘" 问题:默认情况下,AI 会倾向于生成最常见的代码模式(比如 File.ReadAllText),因为它训练数据里这种写法出现频率最高。加上明确规则后,相当于给了 AI 一个 "优先级列表"。

  2. 规则越具体,AI 越听话:"用异步" 太模糊,"所有 I/O 操作必须用 await" 就具体得多。AI 对具体指令的遵守率比抽象原则高 30-50%(这是我个人测试的经验值)。

  3. 微软官方背书:这套规则直接来自 .NET 团队,比社区里的各种 "最佳实践" 更权威。AI 对 "微软官方" 这个词的权重更高(因为训练数据里官方文档的标注质量更高)。

变体和扩展用法

变体 1:针对特定框架的提示词

如果你在用 ASP.NET Core,可以加几条:

markdown
1 2 3 4
- 控制器方法返回 `IActionResult` 或 `Task<IActionResult>`
- 使用 `[ApiController]` 和路由属性
- 不要在控制器里直接使用 `HttpContext`,通过注入获取
- 使用 `ProblemDetails` 返回错误信息

变体 2:针对单元测试的提示词

markdown
1 2 3 4
- 使用 xUnit 作为测试框架
- 用 `FluentAssertions` 做断言
- 依赖注入用 `WebApplicationFactory<T>` 或 `AutoFixture`
- 测试方法命名格式:`MethodName_Scenario_ExpectedResult`

变体 3:针对性能敏感场景的提示词

markdown
1 2 3 4
- 避免 LINQ 的 `.ToList()` 和 `.Count()` 链式调用
- 用 `ArrayPool<T>` 处理临时数组
- 热点路径用 `unsafe` 代码(需明确标记)
- 使用 `ValueTask` 而不是 `Task`(当结果可能同步返回时)

注意事项

  1. 提示词不是万能的:AI 仍然可能犯错,尤其是复杂逻辑。生成代码后一定要 review。

  2. 版本兼容性:C# 12 的新特性(如主构造函数)只在 .NET 8+ 可用。如果你的项目是 .NET 6,需要在提示词里指定版本。

  3. 不要过度约束:规则太多会让 AI 变得保守,甚至生成不符合上下文的代码。建议控制在 8-10 条。

  4. **这些规则本质是 "最佳实践"**:如果你有特殊需求(比如性能极致优化),可以覆盖这些规则。

总结

dotnet/skills 项目看起来只是几十行提示词,但它解决了一个实际问题:AI 编程助手不懂 .NET 的潜规则

作为开发者,你不需要等微软更新这个仓库——直接复制上面的模板,根据自己的项目需求调整,就能让 AI 生成的 C# 代码质量提升一个档次。

最后说一句:这个项目刚开源 24 小时,目前只有基础规则。我已经给仓库提了个 PR,建议加上 Blazor 和 MAUI 的专用规则。如果你也有想法,不妨也去贡献一下——毕竟,让 AI 写更好的 C# 代码,对大家都是好事。

dotnet/skills GitHub仓库界面,显示stars和fork数