mirror of
https://github.com/MCCTeam/Minecraft-Console-Client
synced 2026-08-15 13:04:36 +00:00
Update csharp-best-practices skill from C# 12 / .NET 8 to C# 14 / .NET 10
Co-authored-by: milutinke <441903+milutinke@users.noreply.github.com> Agent-Logs-Url: https://github.com/MCCTeam/Minecraft-Console-Client/sessions/8cdef112-415b-4769-877d-61f0e7a300eb
This commit is contained in:
parent
4d51282040
commit
1e2847a48e
1 changed files with 190 additions and 7 deletions
|
|
@ -1,15 +1,15 @@
|
|||
---
|
||||
name: csharp-best-practices
|
||||
description: >
|
||||
C# 12 / .NET 8 coding conventions, idiomatic patterns, and performance best practices
|
||||
C# 14 / .NET 10 coding conventions, idiomatic patterns, and performance best practices
|
||||
for the Minecraft Console Client codebase. Use when writing, reviewing, or modifying C# code.
|
||||
version: 0.3.0
|
||||
version: 0.4.0
|
||||
---
|
||||
|
||||
# C# 12 / .NET 8 Best Practices
|
||||
# C# 14 / .NET 10 Best Practices
|
||||
|
||||
Target: **.NET 8**, **C# 12**, nullable enabled.
|
||||
Sources: [MS C# Conventions](https://learn.microsoft.com/en-us/dotnet/csharp/fundamentals/coding-style/coding-conventions) · [.NET Runtime Style](https://github.com/dotnet/runtime/blob/main/docs/coding-guidelines/coding-style.md) · [C# 12 Docs](https://learn.microsoft.com/en-us/dotnet/csharp/whats-new/csharp-12) · [.NET 8 Perf](https://devblogs.microsoft.com/dotnet/performance-improvements-in-net-8/)
|
||||
Target: **.NET 10**, **C# 14**, nullable enabled.
|
||||
Sources: [MS C# Conventions](https://learn.microsoft.com/en-us/dotnet/csharp/fundamentals/coding-style/coding-conventions) · [.NET Runtime Style](https://github.com/dotnet/runtime/blob/main/docs/coding-guidelines/coding-style.md) · [C# 14 Proposals](https://github.com/dotnet/csharplang/blob/main/Language-Version-History.md) · [C# 13 Docs](https://learn.microsoft.com/en-us/dotnet/csharp/whats-new/csharp-13)
|
||||
|
||||
## Naming
|
||||
|
||||
|
|
@ -41,6 +41,188 @@ public int packet_count { get; set; } // snake_case
|
|||
public async Task<bool> Connect(CancellationToken ct) { } // missing Async suffix
|
||||
```
|
||||
|
||||
## C# 14 Features
|
||||
|
||||
### Extension Members (C# 14)
|
||||
|
||||
Declare extension methods, properties, and operators inside `extension(...)` blocks. Replaces `this`-parameter pattern for new extensions.
|
||||
|
||||
```csharp
|
||||
// CORRECT: extension property + method (C# 14)
|
||||
public static class EntityExtensions
|
||||
{
|
||||
extension(Entity entity)
|
||||
{
|
||||
public bool IsAlive => entity.Health > 0;
|
||||
public void Heal(int amount) => entity.Health = Math.Min(entity.Health + amount, 20);
|
||||
}
|
||||
extension<T>(IEnumerable<T> items)
|
||||
{
|
||||
public bool IsEmpty => !items.GetEnumerator().MoveNext();
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
```csharp
|
||||
// WRONG: classic extension method when C# 14 extension block is available
|
||||
public static bool IsAlive(this Entity entity) => entity.Health > 0;
|
||||
```
|
||||
|
||||
### `field` Keyword in Properties (C# 14)
|
||||
|
||||
Access the auto-generated backing field without declaring it. Mix auto and full accessors.
|
||||
|
||||
```csharp
|
||||
// CORRECT: lazy init with field keyword
|
||||
public string DisplayName => field ??= ComputeDisplayName();
|
||||
|
||||
// CORRECT: INotifyPropertyChanged pattern
|
||||
public bool IsConnected
|
||||
{
|
||||
get;
|
||||
set
|
||||
{
|
||||
if (field == value) return;
|
||||
field = value;
|
||||
OnPropertyChanged();
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
```csharp
|
||||
// WRONG: manual backing field when field keyword suffices
|
||||
private string? _displayName;
|
||||
public string DisplayName => _displayName ??= ComputeDisplayName();
|
||||
```
|
||||
|
||||
### Null-Conditional Assignment (C# 14)
|
||||
|
||||
Assign through `?.` — RHS is only evaluated when receiver is non-null.
|
||||
|
||||
```csharp
|
||||
// CORRECT: null-conditional assignment
|
||||
player?.Health = 20;
|
||||
connection?.OnDisconnect += HandleDisconnect;
|
||||
inventory?[slot] = newItem;
|
||||
```
|
||||
|
||||
```csharp
|
||||
// WRONG: manual null check for simple assignment
|
||||
if (player is not null)
|
||||
player.Health = 20;
|
||||
```
|
||||
|
||||
### Simple Lambda Parameters with Modifiers (C# 14)
|
||||
|
||||
Omit types on lambda parameters while still applying modifiers.
|
||||
|
||||
```csharp
|
||||
// CORRECT: modifiers without explicit types
|
||||
TryParse<int> parse = (text, out result) => int.TryParse(text, out result);
|
||||
ReadOnlySpan<int> data = [1, 2, 3];
|
||||
ProcessSpan((scoped span) => span.Length);
|
||||
```
|
||||
|
||||
```csharp
|
||||
// WRONG: fully explicit types just for a modifier
|
||||
TryParse<int> parse = (string text, out int result) => int.TryParse(text, out result);
|
||||
```
|
||||
|
||||
### First-Class Span Types (C# 14)
|
||||
|
||||
Implicit conversions between `T[]`, `Span<T>`, and `ReadOnlySpan<T>` — no explicit cast needed. Extension methods on `ReadOnlySpan<T>` apply to arrays and spans automatically.
|
||||
|
||||
```csharp
|
||||
// CORRECT: pass array where ReadOnlySpan<T> is expected (C# 14)
|
||||
int[] data = [1, 2, 3];
|
||||
bool found = data.StartsWith(1); // ReadOnlySpan<int> extension resolved
|
||||
ReadOnlySpan<byte> span = stackalloc byte[4];
|
||||
```
|
||||
|
||||
### Unbound Generics in `nameof` (C# 14)
|
||||
|
||||
```csharp
|
||||
// CORRECT: no need to pick a dummy type argument
|
||||
string name = nameof(Dictionary<,>); // "Dictionary"
|
||||
string prop = nameof(List<>.Count); // "Count"
|
||||
```
|
||||
|
||||
```csharp
|
||||
// WRONG: arbitrary type argument just to satisfy nameof
|
||||
string name = nameof(Dictionary<object, object>);
|
||||
```
|
||||
|
||||
### Partial Events and Constructors (C# 14)
|
||||
|
||||
Separate declaration from implementation for source-generator scenarios.
|
||||
|
||||
```csharp
|
||||
// CORRECT: partial constructor for source-gen interop
|
||||
partial class ServerConnection
|
||||
{
|
||||
partial ServerConnection(string host, int port);
|
||||
}
|
||||
partial class ServerConnection
|
||||
{
|
||||
partial ServerConnection(string host, int port) { /* generated */ }
|
||||
}
|
||||
```
|
||||
|
||||
### `#:` Ignored Directives (C# 14)
|
||||
|
||||
For file-based `dotnet run app.cs` programs — ignored by the compiler.
|
||||
|
||||
```csharp
|
||||
#!/usr/bin/dotnet run
|
||||
#:package System.CommandLine@2.0.0-*
|
||||
Console.WriteLine("Hello");
|
||||
```
|
||||
|
||||
## C# 13 Features
|
||||
|
||||
### `Lock` Object (C# 13)
|
||||
|
||||
Use `System.Threading.Lock` instead of `lock(obj)` on arbitrary objects.
|
||||
|
||||
```csharp
|
||||
// CORRECT: dedicated Lock type
|
||||
private readonly Lock _lock = new();
|
||||
public void Enqueue(ChatMessage msg) { lock (_lock) _queue.Add(msg); }
|
||||
```
|
||||
|
||||
```csharp
|
||||
// WRONG: locking on an object reference
|
||||
private readonly object _syncRoot = new();
|
||||
lock (_syncRoot) { }
|
||||
```
|
||||
|
||||
### `params` Collections (C# 13)
|
||||
|
||||
`params` now works with `ReadOnlySpan<T>`, `Span<T>`, `IEnumerable<T>`, and other collection types.
|
||||
|
||||
```csharp
|
||||
// CORRECT: params span avoids array allocation
|
||||
public void Log(params ReadOnlySpan<string> messages)
|
||||
{
|
||||
foreach (var msg in messages) Console.WriteLine(msg);
|
||||
}
|
||||
```
|
||||
|
||||
### Partial Properties (C# 13)
|
||||
|
||||
```csharp
|
||||
// CORRECT: partial property for source generators
|
||||
partial class Config
|
||||
{
|
||||
public partial string Host { get; set; }
|
||||
}
|
||||
partial class Config
|
||||
{
|
||||
public partial string Host { get => _host; set => _host = value; }
|
||||
private string _host = "";
|
||||
}
|
||||
```
|
||||
|
||||
## C# 12 Features
|
||||
|
||||
### Primary Constructors
|
||||
|
|
@ -518,7 +700,7 @@ for (int i = 0; i < data.Length; i++)
|
|||
int found = data.ToArray().Count(b => b == target);
|
||||
```
|
||||
|
||||
## Performance (.NET 8)
|
||||
## Performance (.NET 8+)
|
||||
|
||||
### Span\<T\> / Memory\<T\>
|
||||
|
||||
|
|
@ -617,11 +799,12 @@ foreach (var s in items) combined += s + ", ";
|
|||
| Scenario | Type | Notes |
|
||||
|---|---|---|
|
||||
| General key-value | `Dictionary<K,V>` | O(1) lookup |
|
||||
| Build once, read many | `FrozenDictionary<K,V>` | .NET 8; faster reads |
|
||||
| Build once, read many | `FrozenDictionary<K,V>` | .NET 8+; faster reads |
|
||||
| Thread-safe | `ConcurrentDictionary<K,V>` | Lock-free reads |
|
||||
| Immutable snapshots | `ImmutableDictionary<K,V>` | Persistent structure |
|
||||
| Membership test | `HashSet<T>` / `FrozenSet<T>` | FrozenSet for static |
|
||||
| Priority queue | `PriorityQueue<E,P>` | .NET 6+ |
|
||||
| Synchronisation | `System.Threading.Lock` | C# 13; prefer over `lock(obj)` |
|
||||
| Producer-consumer | `Channel<T>` | Over `BlockingCollection<T>` |
|
||||
| Temp buffer | `ArrayPool<T>` / `stackalloc` | Zero/low alloc |
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue