Skip to content

Commit 2598d9a

Browse files
committed
Update CLAUDE.md with .NET 10 best practices from recent research
1 parent 8d11a36 commit 2598d9a

File tree

1 file changed

+207
-0
lines changed

1 file changed

+207
-0
lines changed

CLAUDE.md

Lines changed: 207 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -149,3 +149,210 @@ Keep commit messages clear and concise:
149149
- Auto-publish to NuGet on push to main (patches only)
150150
- Auto-create GitHub Release on git tags for major/minor versions
151151
- Dependabot for dependency updates
152+
153+
## .NET 10 Best Practices (Research Updated: November 2025)
154+
155+
### Key Improvements in .NET 10
156+
157+
**Performance:**
158+
- AsyncTaskMethodBuilder optimizations - reduced async overhead
159+
- ValueTask<T> expansion - zero allocations for synchronous completions
160+
- Array enumeration inlining - faster iterations
161+
- Background GC compaction improvements - reduced pause times
162+
163+
**C# 14 Features:**
164+
- Field-backed properties with `field` keyword
165+
- Extension members (properties, static methods)
166+
- Null-conditional assignment operator `?.=`
167+
- Implicit Span conversions
168+
169+
**ASP.NET Core 10:**
170+
- Native SSE support via `TypedResults.ServerSentEvents()`
171+
- OpenAPI 3.1 with JSON Schema 2020-12
172+
- Cookie authentication behavior changes (401/403 instead of redirects for APIs)
173+
- Built-in validation support for minimal APIs
174+
175+
### Library-Specific Recommendations
176+
177+
#### SourceLink Configuration (Critical for Debugging)
178+
Add to `.csproj` for better consumer debugging experience:
179+
```xml
180+
<PropertyGroup>
181+
<PublishRepositoryUrl>true</PublishRepositoryUrl>
182+
<EmbedUntrackedSources>true</EmbedUntrackedSources>
183+
</PropertyGroup>
184+
185+
<ItemGroup>
186+
<PackageReference Include="Microsoft.SourceLink.GitHub" Version="*" PrivateAssets="All"/>
187+
</ItemGroup>
188+
```
189+
190+
#### Nullable Reference Types
191+
- New projects have `<Nullable>enable</Nullable>` by default in .NET 10
192+
- Use "Boy Scout Rule" for existing code: enable file-by-file with `#nullable enable`
193+
- Avoid null-forgiving operator `!` when possible
194+
195+
#### NuGet Audit (.NET 10 Feature)
196+
- Automatically audits transitive dependencies for security issues
197+
- Run `dotnet package update --vulnerable` regularly
198+
- Integrates with GitHub Advisory Database
199+
200+
### Async/Await Best Practices
201+
202+
#### Channel<T> Patterns
203+
**Current Implementation is Good, but Consider:**
204+
205+
1. **Add Channel Options** for better control:
206+
```csharp
207+
var options = new UnboundedChannelOptions
208+
{
209+
SingleReader = true, // Each client has one reader
210+
SingleWriter = false, // Multiple publishers
211+
AllowSynchronousContinuations = false // Avoid blocking publisher
212+
};
213+
```
214+
215+
2. **Handle Channel Completion**:
216+
```csharp
217+
public void Publish(T item)
218+
{
219+
foreach (var (clientId, channel) in _channels)
220+
{
221+
if (!channel.Writer.TryWrite(item))
222+
{
223+
// Channel completed or full - remove dead channels
224+
_channels.TryRemove(clientId, out _);
225+
}
226+
}
227+
}
228+
```
229+
230+
#### IAsyncEnumerable Guidelines
231+
- Use `[EnumeratorCancellation]` attribute on CancellationToken parameters
232+
- Methods returning `IAsyncEnumerable<T>` should end with "Async" suffix
233+
- Never call `.Result` or `.Wait()` - deadlock risk
234+
- Implement `IAsyncDisposable` for resources
235+
236+
#### ValueTask Optimization
237+
- Use `ValueTask<T>` for high-throughput scenarios (>90% synchronous completion)
238+
- .NET 10 has zero-allocation ValueTask for synchronous paths
239+
- Only apply after benchmarking shows benefit
240+
241+
### Testing Best Practices
242+
243+
#### Avoid Flaky Tests
244+
**Root cause of test flakiness:**
245+
- 54% race conditions (use condition-based waits, not arbitrary delays)
246+
- 30% timing issues (increase timeouts for CI environments)
247+
- 16% shared state pollution
248+
249+
**Solution - Polling Helper Pattern:**
250+
```csharp
251+
public static async Task WaitUntilAsync(
252+
Func<bool> condition,
253+
TimeSpan? timeout = null,
254+
string? timeoutMessage = null,
255+
CancellationToken cancellationToken = default)
256+
{
257+
timeout ??= TimeSpan.FromSeconds(10);
258+
using var cts = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken);
259+
cts.CancelAfter(timeout.Value);
260+
261+
try
262+
{
263+
while (!condition())
264+
{
265+
await Task.Delay(TimeSpan.FromMilliseconds(50), cts.Token);
266+
}
267+
}
268+
catch (OperationCanceledException) when (!cancellationToken.IsCancellationRequested)
269+
{
270+
throw new TimeoutException(timeoutMessage ?? "Condition not met within timeout");
271+
}
272+
}
273+
```
274+
275+
**Usage:**
276+
```csharp
277+
// Instead of: while (sseStream.ClientCount == 0) await Task.Delay(50);
278+
await WaitUntilAsync(() => sseStream.ClientCount > 0, TimeSpan.FromSeconds(10));
279+
```
280+
281+
#### Testcontainers with IAsyncLifetime
282+
**Best practice for shared containers:**
283+
```csharp
284+
public sealed class RabbitMqFixture : IAsyncLifetime
285+
{
286+
private readonly RabbitMqContainer _container = new RabbitMqBuilder()
287+
.WithImage("rabbitmq:3.13-management")
288+
.Build();
289+
290+
public string ConnectionString => _container.GetConnectionString();
291+
292+
public async Task InitializeAsync() => await _container.StartAsync();
293+
public async Task DisposeAsync() => await _container.DisposeAsync();
294+
}
295+
296+
[CollectionDefinition("RabbitMQ Collection")]
297+
public class RabbitMqCollection : ICollectionFixture<RabbitMqFixture> { }
298+
```
299+
300+
**Benefits:**
301+
- Container started once per test collection (faster)
302+
- Proper async lifecycle management
303+
- Automatic cleanup
304+
305+
#### xUnit v3 Features
306+
- Use `TestContext.Current.CancellationToken` for test timeouts
307+
- Use `IAsyncLifetime` for async setup/teardown
308+
- Tests automatically cancelled after timeout
309+
310+
### RabbitMQ Integration Patterns
311+
312+
#### Core Principles
313+
- **Publish to exchanges, not queues** - enables flexible routing
314+
- Use `BasicAck` to confirm successful processing
315+
- Use `BasicNack` with requeue for transient failures
316+
- Implement Dead Letter Exchange (DLX) for permanent failures
317+
318+
#### Retry Patterns
319+
**Quorum Queues (Modern Approach):**
320+
- Set "Delivery limit" argument for automatic retry limits
321+
- Automatically dead-letters after max retries
322+
323+
**Exponential Backoff:**
324+
- Use multiple wait queues with TTL: 1min, 5min, 15min
325+
- Each queue dead-letters back to application exchange
326+
- Track retry count via `x-death` header (RabbitMQ 3.8+)
327+
328+
#### Framework Considerations
329+
- For complex scenarios, consider NServiceBus or MassTransit
330+
- Provide built-in retry, saga, and error handling patterns
331+
332+
### Security & Breaking Changes
333+
334+
#### ASP.NET Core 10 Authentication
335+
- Unauthenticated API requests now return 401/403 instead of redirecting
336+
- Applies to endpoints with `IApiEndpointMetadata`
337+
- If exposing HTTP endpoints, ensure proper 401/403 handling
338+
339+
#### NuGet Package Security
340+
- Always use SPDX license expressions (not deprecated LicenseUrl)
341+
- Include README.md in package for better first impressions
342+
- Enable NuGet audit in CI/CD pipelines
343+
344+
### Sources & References
345+
346+
**Official Microsoft:**
347+
- .NET 10 GA Release (November 2025)
348+
- ASP.NET Core 10 Release Notes
349+
- C# 14 What's New
350+
351+
**Community Experts:**
352+
- Stephen Toub - .NET 10 Performance Improvements
353+
- Stephen Cleary - Async/Await Best Practices
354+
- Andrew Lock - .NET 10 Deep Dive Series
355+
- Milan Jovanovic - Testcontainers & Integration Testing
356+
- Khalid Abuhakmeh - Server-Sent Events in .NET 10
357+
358+
**Last Updated:** November 20, 2025

0 commit comments

Comments
 (0)