| name | dotnet-agent-gotchas |
| description | Generating or modifying .NET code. Common agent mistakes: async, NuGet, deprecated APIs, DI. |
dotnet-agent-gotchas
Overview / Scope Boundary
Common mistakes AI agents make when generating or modifying .NET code, organized by category. Each category provides a brief warning, anti-pattern code, corrected code, and a cross-reference to the canonical skill that owns the deep guidance. This skill does NOT provide full implementation walkthroughs -- it surfaces the mistake and points to the right skill.
Out of scope: Deep async/await patterns (owned by [skill:dotnet-csharp-async-patterns]), full dependency injection guidance (owned by [skill:dotnet-csharp-dependency-injection]), NRT usage patterns (owned by [skill:dotnet-csharp-nullable-reference-types]), source generator authoring (owned by [skill:dotnet-csharp-source-generators]), test framework features (owned by [skill:dotnet-testing-strategy]), security vulnerability mitigation (owned by [skill:dotnet-security-owasp]).
Prerequisites
.NET 8.0+ SDK. Familiarity with SDK-style projects and C# language features.
Cross-references: [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].
Category 1: Async/Await Misuse
Warning: Agents frequently block on async methods using .Result or .Wait(), causing deadlocks in ASP.NET Core and UI contexts. Another common mistake is fire-and-forget calls that silently swallow exceptions.
Anti-Pattern
public Order GetOrder(int id)
{
var order = _repository.GetOrderAsync(id).Result;
return order;
}
public void ProcessOrder(Order order)
{
_ = _emailService.SendConfirmationAsync(order);
}
Corrected
public async Task<Order> GetOrderAsync(int id, CancellationToken ct = default)
{
var order = await _repository.GetOrderAsync(id, ct);
return order;
}
public async Task ProcessOrderAsync(Order order, CancellationToken ct = default)
{
await _emailService.SendConfirmationAsync(order, ct);
}
See [skill:dotnet-csharp-async-patterns] for full async/await guidance including ValueTask, ConfigureAwait, and cancellation propagation.
Category 2: NuGet Package Errors
Warning: Agents generate incorrect package names, reference pre-release versions without opt-in, or add packages that have been deprecated/replaced. ASP.NET Core shared-framework packages must match the project TFM major version.
Anti-Pattern
<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" />
Corrected
<PackageReference Include="Microsoft.EntityFrameworkCore" Version="9.0.0" />
<PackageReference Include="Microsoft.AspNetCore.Mvc.Testing" />
<PackageReference Include="Microsoft.AspNetCore.OpenApi" Version="9.0.0" />
See [skill:dotnet-csproj-reading] for project file conventions and central package management guidance.
Category 3: Deprecated API Usage
Warning: Agents generate code using deprecated and insecure APIs: BinaryFormatter (CVE-prone deserialization), WebClient (replaced by HttpClient), and older cryptography APIs (RNGCryptoServiceProvider, SHA1CryptoServiceProvider).
Anti-Pattern
var formatter = new BinaryFormatter();
formatter.Serialize(stream, data);
var client = new WebClient();
var html = client.DownloadString("https://example.com");
using var rng = new RNGCryptoServiceProvider();
rng.GetBytes(buffer);
Corrected
var json = JsonSerializer.Serialize(data);
await File.WriteAllTextAsync("data.json", json);
public class MyService(HttpClient httpClient)
{
public async Task<string> GetHtmlAsync(CancellationToken ct = default)
=> await httpClient.GetStringAsync("https://example.com", ct);
}
RandomNumberGenerator.Fill(buffer);
See [skill:dotnet-security-owasp] for the full deprecated security pattern catalog and OWASP mitigations.
Category 4: Project Structure Mistakes
Warning: Agents use wrong SDK types, add PackageReference entries for framework-included libraries, or create broken ProjectReference paths.
Anti-Pattern
<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" />
Corrected
<Project Sdk="Microsoft.NET.Sdk.Web">
<PropertyGroup>
<TargetFramework>net9.0</TargetFramework>
</PropertyGroup>
</Project>
<ProjectReference Include="..\MyApp.Core\MyApp.Core.csproj" />
See [skill:dotnet-project-structure] for SDK types, project organization, and project reference conventions.
Category 5: Nullable Reference Type Annotation Errors
Warning: Agents misuse the null-forgiving operator (!) to silence warnings instead of fixing nullability, or forget to enable the nullable context.
Anti-Pattern
public string GetUserName(int id)
{
var user = _db.Users.Find(id);
return user!.Name;
}
public string? GetOptionalValue() => null;
Corrected
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>
See [skill:dotnet-csharp-nullable-reference-types] for full NRT usage patterns and annotation strategies.
Category 6: Source Generator Misconfiguration
Warning: Agents forget to mark classes as partial when source generators need to augment them, or use incorrect output types that prevent generator output from compiling.
Anti-Pattern
[JsonSerializable(typeof(WeatherForecast))]
internal class WeatherJsonContext : JsonSerializerContext
{
}
[LoggerMessage(EventId = 1, Level = LogLevel.Information, Message = "Processing {Item}")]
public static partial struct LogMessages
{
}
Corrected
[JsonSerializable(typeof(WeatherForecast))]
internal partial class WeatherJsonContext : JsonSerializerContext
{
}
public static partial class Log
{
[LoggerMessage(EventId = 1, Level = LogLevel.Information, Message = "Processing {Item}")]
public static partial void ProcessingItem(ILogger logger, string item);
}
See [skill:dotnet-csharp-source-generators] for source generator configuration, diagnostics, and debugging.
Category 7: Trimming/AOT Warning Suppression
Warning: Agents suppress trimming and AOT warnings with #pragma or [UnconditionalSuppressMessage] instead of fixing the underlying reflection/dynamic usage. Suppression hides runtime failures in published apps.
Anti-Pattern
#pragma warning disable IL2026
var type = Type.GetType(typeName);
var instance = Activator.CreateInstance(type!);
#pragma warning restore IL2026
Corrected
public T CreateInstance<T>() where T : new()
{
return new T();
}
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>
See [skill:dotnet-csproj-reading] for MSBuild property guidance on trimming and AOT configuration.
Category 8: Test Organization Anti-Patterns
Warning: Agents put test classes in production projects, use wrong test SDK configurations, or mix test framework attributes incorrectly.
Anti-Pattern
namespace MyApp.Api;
public class OrderServiceTests
{
[Fact]
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>
Corrected
<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>
See [skill:dotnet-testing-strategy] for test organization, naming conventions, and test type decision guidance.
Category 9: DI Registration Errors
Warning: Agents forget to register services, use wrong lifetimes (singleton capturing scoped), or create captive dependencies that cause memory leaks and concurrency bugs.
Anti-Pattern
builder.Services.AddSingleton<OrderProcessor>();
builder.Services.AddScoped<IOrderRepository, OrderRepository>();
public class OrderProcessor(IOrderRepository repo)
{
public async Task ProcessAsync(int orderId, CancellationToken ct)
{
var order = await repo.GetByIdAsync(orderId, ct);
}
}
Corrected
builder.Services.AddScoped<OrderProcessor>();
builder.Services.AddScoped<IOrderRepository, OrderRepository>();
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);
}
}
See [skill:dotnet-csharp-dependency-injection] for lifetime rules, registration patterns, and service scope management.
Slopwatch Anti-Patterns
These are patterns that indicate an agent is hiding problems rather than fixing them. Every code review should check for these. See [skill:dotnet-slopwatch] for the automated quality gate that detects these patterns.
1. Disabled or Skipped Tests
[Fact(Skip = "Flaky, will fix later")]
public void CriticalBusinessLogic_WorksCorrectly() { }
#if false
[Fact]
public void ImportantEdgeCase() { }
#endif
Fix: Investigate and fix the underlying issue. If a test is genuinely flaky due to timing, use [Retry] (xUnit v3) or fix the non-determinism. Never disable tests to achieve a green build.
2. Warning Suppressions
#pragma warning disable CS8600, CS8602, CS8604 // suppress all nullability warnings
var result = GetData();
result.Process();
#pragma warning restore CS8600, CS8602, CS8604
Fix: Address the underlying nullability or trim issues. Add proper null checks, use nullable annotations correctly, or apply [DynamicallyAccessedMembers] for trim warnings.
3. Empty Catch Blocks
try
{
await _service.ProcessAsync(data, ct);
}
catch (Exception) { }
catch (Exception ex)
{
}
Fix: At minimum, log the exception. Prefer catching specific exception types and handling them appropriately.
4. Silenced Analyzers Without Justification
[SuppressMessage("Design", "CA1062")]
public void Process(string input) { }
Fix: Fix the code to satisfy the analyzer rule, or provide a documented justification in the suppression attribute: [SuppressMessage("Design", "CA1062", Justification = "Input validated by middleware")].
5. Removed Assertions from Tests
[Fact]
public async Task CreateOrder_Succeeds()
{
var service = new OrderService();
await service.CreateOrderAsync(new Order());
}
Fix: Every test must have at least one assertion that validates the expected behavior. If the test is for side effects, assert on the side effect (database state, published events, log output).
Cross-References
- [skill:dotnet-csharp-async-patterns] -- async/await deep patterns,
ValueTask, cancellation
- [skill:dotnet-csharp-dependency-injection] -- DI lifetime rules, registration patterns, scope management
- [skill:dotnet-csharp-nullable-reference-types] -- NRT annotations, nullable context, flow analysis
- [skill:dotnet-csharp-source-generators] -- generator configuration, partial class requirements, diagnostics
- [skill:dotnet-testing-strategy] -- test type decisions, organization, naming conventions
- [skill:dotnet-security-owasp] -- OWASP mitigations, deprecated security API catalog
References