mcpField
D
dotnet-agent-gotchas

dotnet-agent-gotchas

github.com/wshaddix开发8679 次安装35 周前更新
代码仓库

关于 dotnet-agent-gotchas

面向生成或修改 .NET 代码的 AI Agent,汇总常见错误,并以警示、反例、修正示例和相关技能索引帮助代码审查。它聚焦发现问题,不提供完整实现教程。

主要功能

  1. 01涵盖异步、NuGet 与过时 API
  2. 02检查项目结构和可空注解
  3. 03提示源生成器及 AOT 配置问题
  4. 04列出测试、依赖注入和质量反模式

适用场景

  1. 01审查 AI Agent 生成的 .NET 代码
  2. 02排查测试组织或依赖注入错误
  3. 03查找问题对应的深入技能指南

安装

复制下面的命令,在终端中运行。

$npx skills add https://github.com/wshaddix/dotnet-skills --skill dotnet-agent-gotchas

安装后在 Claude Code 中输入/dotnet-agent-gotchas确认已加载。

详细介绍

dotnet-agent-gotchas

概述 / 范围界定

AI Agent 在生成或修改 .NET 代码时常犯的错误,按类别整理。每个类别都提供简短警告、反模式代码、修正后的代码,以及指向负责深入指导的规范技能的交叉引用。本技能不提供完整的实现教程——只指出错误,并指引至正确的技能。

**不在范围内:**深入的 async/await 模式(由 [skill:dotnet-csharp-async-patterns] 负责)、完整的依赖注入指导(由 [skill:dotnet-csharp-dependency-injection] 负责)、NRT 使用模式(由 [skill:dotnet-csharp-nullable-reference-types] 负责)、源生成器编写(由 [skill:dotnet-csharp-source-generators] 负责)、测试框架功能(由 [skill:dotnet-testing-strategy] 负责)、安全漏洞缓解(由 [skill:dotnet-security-owasp] 负责)。

前置条件

.NET 8.0+ SDK。熟悉 SDK 风格的项目和 C# 语言功能。

交叉引用:[skill:dotnet-csharp-async-patterns]、[skill:dotnet-csharp-dependency-injection]、[skill:dotnet-csharp-nullable-reference-types]、[skill:dotnet-csharp-source-generators]、[skill:dotnet-testing-strategy]、[skill:dotnet-security-owasp]。


类别 1:误用 Async/Await

**警告:**Agent 经常使用 .Result 或 .Wait() 阻塞异步方法,导致 ASP.NET Core 和 UI 上下文中出现死锁。另一个常见错误是调用即忘(fire-and-forget),导致异常被悄然吞掉。

反模式
// WRONG: blocking on async -- deadlock risk in synchronization contexts
public Order GetOrder(int id)
{
    var order = _repository.GetOrderAsync(id).Result; // DEADLOCK
    return order;
}

// WRONG: fire-and-forget with no error handling
public void ProcessOrder(Order order)
{
    _ = _emailService.SendConfirmationAsync(order); // exception silently lost
}
修正后
// CORRECT: async all the way
public async Task<Order> GetOrderAsync(int id, CancellationToken ct = default)
{
    var order = await _repository.GetOrderAsync(id, ct);
    return order;
}

// CORRECT: background work via IHostedService or explicit error handling
public async Task ProcessOrderAsync(Order order, CancellationToken ct = default)
{
    await _emailService.SendConfirmationAsync(order, ct);
}

有关完整的 async/await 指导,包括 ValueTask、ConfigureAwait 和取消传播,请参阅 [skill:dotnet-csharp-async-patterns]。


类别 2:NuGet 包错误

**警告:**Agent 生成错误的包名称、未选择加入便引用预发布版本,或添加已弃用/替代的包。ASP.NET Core 共享框架包的主版本必须与项目 TFM 的主版本一致。

反模式

<PackageReference Include="EntityFrameworkCore" Version="9.0.0" />


<PackageReference Include="Microsoft.AspNetCore.Mvc.Testing" Version="9.0.0" />



<PackageReference Include="Swashbuckle.AspNetCore" Version="7.0.0" />

修正后

<PackageReference Include="Microsoft.EntityFrameworkCore" Version="9.0.0" />


<PackageReference Include="Microsoft.AspNetCore.Mvc.Testing" />



<PackageReference Include="Microsoft.AspNetCore.OpenApi" Version="9.0.0" />

有关项目文件约定和集中式包管理指导,请参阅 [skill:dotnet-csproj-reading]。


类别 3:使用已弃用的 API

**警告:**Agent 生成使用已弃用且不安全 API 的代码:BinaryFormatter(反序列化容易导致 CVE)、WebClient(已由 HttpClient 替代),以及较旧的加密 API(RNGCryptoServiceProvider、SHA1CryptoServiceProvider)。

反模式
// WRONG: BinaryFormatter is banned in .NET 8+ (SYSLIB0011)
var formatter = new BinaryFormatter();
formatter.Serialize(stream, data);

// WRONG: WebClient is obsolete -- use HttpClient via IHttpClientFactory
var client = new WebClient();
var html = client.DownloadString("https://example.com");

// WRONG: obsolete crypto API (SYSLIB0023)
using var rng = new RNGCryptoServiceProvider();
rng.GetBytes(buffer);
修正后
// CORRECT: use System.Text.Json for serialization
var json = JsonSerializer.Serialize(data);
await File.WriteAllTextAsync("data.json", json);

// CORRECT: use IHttpClientFactory (registered via DI)
public class MyService(HttpClient httpClient)
{
    public async Task<string> GetHtmlAsync(CancellationToken ct = default)
        => await httpClient.GetStringAsync("https://example.com", ct);
}

// CORRECT: modern RandomNumberGenerator (static API)
RandomNumberGenerator.Fill(buffer);

有关完整的已弃用安全模式目录和 OWASP 缓解措施,请参阅 [skill:dotnet-security-owasp]。


类别 4:项目结构错误

**警告:**Agent 使用错误的 SDK 类型、为框架已包含的库添加 PackageReference 条目,或创建无效的 ProjectReference 路径。

反模式

<Project Sdk="Microsoft.NET.Sdk">
  <PropertyGroup>
    <OutputType>Exe</OutputType>
    <TargetFramework>net9.0</TargetFramework>
  </PropertyGroup>
  
</Project>


<PackageReference Include="Microsoft.Extensions.Logging" Version="9.0.0" />



<ProjectReference Include="..\..\Core\MyApp.Core.csproj" />

修正后

<Project Sdk="Microsoft.NET.Sdk.Web">
  <PropertyGroup>
    <TargetFramework>net9.0</TargetFramework>
  </PropertyGroup>
</Project>





<ProjectReference Include="..\MyApp.Core\MyApp.Core.csproj" />

有关 SDK 类型、项目组织和项目引用约定,请参阅 [skill:dotnet-project-structure]。


类别 5:可空引用类型注解错误

**警告:**Agent 滥用 null-forgiving 运算符(!)来消除警告,而不是修正可空性;或者忘记启用可空上下文。

反模式
// WRONG: null-forgiving operator hides a real null risk
public string GetUserName(int id)
{
    var user = _db.Users.Find(id);
    return user!.Name; // NullReferenceException if user not found
}

// WRONG: nullable not enabled, so annotations are meaningless
// Missing <Nullable>enable</Nullable> in .csproj
public string? GetOptionalValue() => null; // no compiler warnings without nullable context
修正后
// CORRECT: handle null explicitly
public string GetUserName(int id)
{
    var user = _db.Users.Find(id);
    if (user is null)
    {
        throw new InvalidOperationException($"User {id} not found.");
    }

    return user.Name;
}

<PropertyGroup>
  <Nullable>enable</Nullable>
</PropertyGroup>

有关完整的 NRT 使用模式和注解策略,请参阅 [skill:dotnet-csharp-nullable-reference-types]。


类别 6:源生成器配置错误

**警告:**当源生成器需要扩展类时,Agent 忘记将类标记为 partial;或者使用错误的输出类型,导致生成器输出无法编译。

反模式
// WRONG: missing partial keyword -- source generator cannot augment this class
[JsonSerializable(typeof(WeatherForecast))]
internal class WeatherJsonContext : JsonSerializerContext
{
}

// WRONG: generator expects a class but agent declared a struct
[LoggerMessage(EventId = 1, Level = LogLevel.Information, Message = "Processing {Item}")]
public static partial struct LogMessages // struct is invalid for LoggerMessage
{
}
修正后
// CORRECT: partial class allows source generator to emit companion code
[JsonSerializable(typeof(WeatherForecast))]
internal partial class WeatherJsonContext : JsonSerializerContext
{
}

// CORRECT: LoggerMessage requires partial method in a partial class
public static partial class Log
{
    [LoggerMessage(EventId = 1, Level = LogLevel.Information, Message = "Processing {Item}")]
    public static partial void ProcessingItem(ILogger logger, string item);
}

有关源生成器配置、诊断和调试,请参阅 [skill:dotnet-csharp-source-generators]。


类别 7:裁剪/AOT 警告抑制

警告: Agent 使用 #pragma 或 [UnconditionalSuppressMessage] 来抑制裁剪和 AOT 警告,而不是修复底层的反射/动态用法。抑制警告会掩盖已发布应用中的运行时故障。

反模式
// WRONG: suppressing trim warning instead of fixing it
#pragma warning disable IL2026
var type = Type.GetType(typeName); // reflection not trim-safe
var instance = Activator.CreateInstance(type!);
#pragma warning restore IL2026

// WRONG: app-level suppression in .csproj hides all trim warnings
// <NoWarn>IL2026;IL2046;IL3050</NoWarn>
修正后
// CORRECT: use compile-time type resolution or [DynamicallyAccessedMembers]
public T CreateInstance<T>() where T : new()
{
    return new T(); // no reflection, trim-safe
}

// For unavoidable reflection, annotate correctly:
public object CreateInstance(
    [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicConstructors)] Type type)
{
    return Activator.CreateInstance(type)
        ?? throw new InvalidOperationException($"Cannot create {type.Name}");
}


<PublishTrimmed>true</PublishTrimmed>
<EnableTrimAnalyzer>true</EnableTrimAnalyzer>

<IsTrimmable>true</IsTrimmable>

请参阅 [skill:dotnet-csproj-reading],了解裁剪和 AOT 配置的 MSBuild 属性指南。


类别 8:测试组织反模式

警告: Agent 将测试类放在生产项目中、使用错误的测试 SDK 配置,或混用测试框架特性。

反模式
// WRONG: test class in the production project (not in a separate test project)
// File: src/MyApp.Api/OrderServiceTests.cs
namespace MyApp.Api;

public class OrderServiceTests
{
    [Fact] // xUnit attribute in production code -- ships test dependencies to users
    public void CalculateTotal_ReturnsCorrectSum() { }
}

<Project Sdk="Microsoft.NET.Sdk">
  <PropertyGroup>
    <TargetFramework>net9.0</TargetFramework>
  </PropertyGroup>
  <ItemGroup>
    <PackageReference Include="xunit.v3" Version="3.2.2" />
    
  </ItemGroup>
</Project>
修正后

<Project Sdk="Microsoft.NET.Sdk">
  <PropertyGroup>
    <TargetFramework>net9.0</TargetFramework>
    <IsTestProject>true</IsTestProject>
  </PropertyGroup>
  <ItemGroup>
    <PackageReference Include="xunit.v3" Version="3.2.2" />
    <PackageReference Include="xunit.runner.visualstudio" Version="3.1.5" />
    <PackageReference Include="Microsoft.NET.Test.Sdk" Version="18.0.1" />
  </ItemGroup>
  <ItemGroup>
    <ProjectReference Include="..\..\src\MyApp.Api\MyApp.Api.csproj" />
  </ItemGroup>
</Project>

请参阅 [skill:dotnet-testing-strategy],了解测试组织、命名约定和测试类型选择指南。


类别 9:DI 注册错误

警告: Agent 忘记注册服务、使用错误的生命周期(单例捕获作用域服务),或创建会导致内存泄漏和并发错误的被俘获依赖项。

反模式
// WRONG: scoped service injected into singleton -- captive dependency
builder.Services.AddSingleton<OrderProcessor>(); // singleton
builder.Services.AddScoped<IOrderRepository, OrderRepository>(); // scoped

public class OrderProcessor(IOrderRepository repo) // repo is captured as singleton!
{
    public async Task ProcessAsync(int orderId, CancellationToken ct)
    {
        var order = await repo.GetByIdAsync(orderId, ct); // same DbContext forever
    }
}

// WRONG: missing registration causes runtime exception
// builder.Services.AddScoped<IOrderRepository, OrderRepository>(); // forgot this line
// InvalidOperationException: Unable to resolve service for type 'IOrderRepository'
修正后
// CORRECT: lifetimes must not capture shorter-lived dependencies
builder.Services.AddScoped<OrderProcessor>(); // scoped, matches repository lifetime
builder.Services.AddScoped<IOrderRepository, OrderRepository>();

// Or if OrderProcessor must be singleton, inject IServiceScopeFactory:
builder.Services.AddSingleton<OrderProcessor>();

public class OrderProcessor(IServiceScopeFactory scopeFactory)
{
    public async Task ProcessAsync(int orderId, CancellationToken ct)
    {
        await using var scope = scopeFactory.CreateAsyncScope();
        var repo = scope.ServiceProvider.GetRequiredService<IOrderRepository>();
        var order = await repo.GetByIdAsync(orderId, ct);
    }
}

请参阅 [skill:dotnet-csharp-dependency-injection],了解生命周期规则、注册模式和服务作用域管理。


Slopwatch 反模式

这些模式表明 Agent 在掩盖问题,而不是修复问题。每次代码审查都应检查这些模式。请参阅 [skill:dotnet-slopwatch],了解用于检测这些模式的自动化质量门禁。

1. 禁用或跳过测试
// RED FLAG: skipping tests to make the build pass
[Fact(Skip = "Flaky, will fix later")] // test never gets fixed
public void CriticalBusinessLogic_WorksCorrectly() { }

// RED FLAG: commenting out failing tests
// [Fact]
// public void CalculateTotal_HandlesNegative() { ... }

// RED FLAG: conditional compilation to hide tests
#if false
[Fact]
public void ImportantEdgeCase() { }
#endif

修复: 调查并修复底层问题。如果测试确实因时序问题而不稳定,请使用 [Retry](xUnit v3)或修复非确定性问题。切勿为了让构建通过而禁用测试。

2. 警告抑制
// RED FLAG: blanket warning suppression
#pragma warning disable CS8600, CS8602, CS8604 // suppress all nullability warnings
var result = GetData();
result.Process();
#pragma warning restore CS8600, CS8602, CS8604

// RED FLAG: project-level suppression hiding real issues
// <NoWarn>CS8618;CS8625;IL2026</NoWarn>

修复: 解决底层的可空性或裁剪问题。添加适当的空值检查、正确使用可空注解,或针对裁剪警告使用 [DynamicallyAccessedMembers]。

3. 空 catch 块
// RED FLAG: swallowing exceptions silently
try
{
    await _service.ProcessAsync(data, ct);
}
catch (Exception) { } // failure is invisible

// RED FLAG: catch-and-ignore with misleading comment
catch (Exception ex)
{
    // TODO: add logging
}

修复: 至少要记录异常。优先捕获特定异常类型,并进行适当处理。

4. 未说明理由地静默分析器
// RED FLAG: suppressing analyzer with no explanation
[SuppressMessage("Design", "CA1062")]
public void Process(string input) { }

// RED FLAG: disabling analyzer rules in .editorconfig globally
// dotnet_diagnostic.CA1062.severity = none

修复: 修改代码以满足分析器规则,或在抑制特性中提供有记录的理由:[SuppressMessage("Design", "CA1062", Justification = "Input validated by middleware")]。

5. 从测试中移除断言
// RED FLAG: test with no assertions -- always passes
[Fact]
public async Task CreateOrder_Succeeds()
{
    var service = new OrderService();
    await service.CreateOrderAsync(new Order());
    // no Assert -- this test proves nothing
}

修复: 每个测试都必须至少有一个断言,用于验证预期行为。如果测试验证的是副作用,则应断言该副作用(数据库状态、已发布事件、日志输出)。


交叉引用

  • [skill:dotnet-csharp-async-patterns] -- async/await 深入模式、ValueTask、取消
  • [skill:dotnet-csharp-dependency-injection] -- DI 生命周期规则、注册模式、作用域管理
  • [skill:dotnet-csharp-nullable-reference-types] -- NRT 注解、可空上下文、流分析
  • [skill:dotnet-csharp-source-generators] -- 生成器配置、分部类要求、诊断
  • [skill:dotnet-testing-strategy] -- 测试类型选择、组织、命名约定
  • [skill:dotnet-security-owasp] -- OWASP 缓解措施、已弃用安全 API 目录

参考资料

评论

支持 Markdown