dotnet-agent-gotchas について
.NETコードを生成・変更するAIエージェント向けのリファレンススキルです。よくある誤りを警告、アンチパターン、修正例、関連スキルへの参照で示し、実装全体の手順ではなく問題の特定に重点を置きます。
主な機能
- 01非同期処理、NuGet、旧式APIの注意点
- 02プロジェクト構成とNullable注釈の確認
- 03ソースジェネレーターとAOT設定の問題
- 04テスト、DI、品質面のアンチパターン
活用シーン
- 01AIエージェントが生成した.NETコードのレビュー
- 02テスト構成やDIの誤りの確認
- 03指摘事項に対応する詳しいスキルの参照
インストール
コマンドをコピーしてターミナルで実行します。
npx skills add https:/ / github.com/ wshaddix/ dotnet-skills --skill dotnet-agent-gotchasインストール後、Claude Code で/dotnet-agent-gotchasを入力して読み込まれたか確認します。
詳細
dotnet-agent-gotchas
概要/対象範囲
AI エージェントが .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 の誤用
警告: エージェントは .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);
}
ValueTask、ConfigureAwait、キャンセルの伝播など、async/await に関する詳しいガイダンスは [skill:dotnet-csharp-async-patterns] を参照してください。
カテゴリ 2:NuGet パッケージのエラー
警告: エージェントは、間違ったパッケージ名を生成したり、明示的な許可なしにプレリリースバージョンを参照したり、非推奨になった、または置き換えられたパッケージを追加したりします。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 の使用
警告: エージェントは、非推奨で安全でない 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:プロジェクト構成の間違い
警告: エージェントは、誤った 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:Nullable Reference Type の注釈エラー
警告: エージェントは、null 許容性を修正せずに警告を抑制するために null 強制演算子(!)を誤用したり、nullable コンテキストを有効にし忘れたりします。
アンチパターン
// 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:ソースジェネレーターの構成ミス
警告: エージェントは、ソースジェネレーターがクラスを拡張する際に、そのクラスを 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 警告の抑制
警告: エージェントが、リフレクションや動的な使用方法の根本的な問題を修正せずに、#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>
トリミングおよび AOT の構成に関する MSBuild プロパティの指針については、[skill:dotnet-csproj-reading] を参照してください。
カテゴリ 8:テスト構成のアンチパターン
警告: エージェントがテストクラスを本番プロジェクトに配置したり、誤ったテスト 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 登録エラー
警告: エージェントがサービスの登録を忘れたり、誤ったライフタイム(スコープ付きサービスを保持するシングルトンなど)を使用したり、メモリリークや並行処理のバグを引き起こすキャプティブ依存関係を作成したりします。
アンチパターン
// 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 のアンチパターン
これらは、エージェントが問題を修正せずに隠していることを示すパターンです。すべてのコードレビューで、これらがないか確認してください。これらのパターンを検出する自動品質ゲートについては、[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>
修正方法: 根本的な null 許容またはトリミングの問題に対処してください。適切な null チェックを追加し、null 許容アノテーションを正しく使用するか、トリミングの警告には [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
}
修正方法: すべてのテストには、期待される動作を検証するアサーションを少なくとも 1 つ含める必要があります。副作用をテストする場合は、その副作用(データベースの状態、公開されたイベント、ログ出力)をアサートしてください。
相互参照
- [skill:dotnet-csharp-async-patterns] -- async/await の高度なパターン、
ValueTask、キャンセル - [skill:dotnet-csharp-dependency-injection] -- DI のライフタイムルール、登録パターン、スコープ管理
- [skill:dotnet-csharp-nullable-reference-types] -- NRT アノテーション、null 許容コンテキスト、フロー分析
- [skill:dotnet-csharp-source-generators] -- ジェネレーターの構成、partial クラスの要件、診断
- [skill:dotnet-testing-strategy] -- テストの種類の選択、構成、命名規則
- [skill:dotnet-security-owasp] -- OWASP の緩和策、非推奨のセキュリティ API カタログ