本文へ移動
cccskills
無料GitHub で公開

csharp-conventions

Modern C# idioms for any .NET project (C# 10+, .NET 6+): nullable reference types, records, readonly structs, primary constructors, pattern matching, async/await with CancellationToken, IDisposable/IAsyncDisposable, file-scoped namespaces, var usage, naming conventions (PascalCase members, _camelCase fields, I-prefixed interfaces), and class design rules. Apply whenever the project is a .NET 6+ project. Stack-agnostic — referenced by every .NET plugin in the marketplace. Use this skill to: - Write self-documenting immutable value types with records and readonly structs. - Handle nullable reference types explicitly to eliminate NullReferenceException at compile time. - Implement async/await correctly with CancellationToken propagation and ConfigureAwait(false) in libraries. - Dispose unmanaged resources correctly via IDisposable / IAsyncDisposable and using statements. - Apply C# pattern matching (switch expressions, property patterns, list patterns) for cleaner branching logic. Do NOT use this skill for: - Framework-specific idioms (ASP.NET Core controllers, minimal APIs, EF Core — those live in aspnet-core-plugin skills). - Build tooling (dotnet CLI, NuGet, csproj) — see csharp-foundation:dotnet-tooling. - Testing patterns — see csharp-foundation:dotnet-testing.

インストール方法を見る

含まれるファイル(1)

  • SKILL.md9.4 KB

SKILL.md(原文)

インストールする前に、エージェントに与えられる指示の中身を確認できます。

C# Conventions (stack-agnostic, C# 10+ / .NET 6+)

This skill encodes idioms that reduce bugs and improve readability in any C# codebase. Apply alongside the active framework plugin's conventions skill (e.g., aspnet-core-plugin:aspnet-conventions).

Detection

Project is C# 10+ / .NET 6+ when:

  • .csproj has <TargetFramework>net6.0</TargetFramework> or higher (net7.0, net8.0, net9.0, net10.0).
  • global.json pins sdk.version to 6.0.x or higher.

Read the .csproj <TargetFramework> before making any version-specific decisions.

Nullable reference types — eliminate null surprises

Enable in every project. New projects get it by default from dotnet new; older projects need a one-time migration.

<!-- .csproj: enable project-wide -->
<PropertyGroup>
  <Nullable>enable</Nullable>
</PropertyGroup>
// Non-nullable: compiler guarantees non-null, no null check needed
public string Name { get; }

// Nullable: caller must check before dereferencing
public string? MiddleName { get; }

// Null-forgiving operator — use only when you have proven non-null
var definitelySet = _cache[key]!;

// Null-conditional + null-coalescing
string display = user?.FullName ?? "Guest";

Never silence nullable warnings with ! without a comment explaining why the value is guaranteed non-null. Prefer redesigning the API to avoid the need.

Records — prefer for value objects and DTOs

Use record (class) for immutable reference-type value objects; readonly record struct for small value types.

// Immutable DTO — all positional parameters become init-only properties
public record Money(decimal Amount, string Currency)
{
    // Compact validation in the record body
    public Money
    {
        if (Amount < 0) throw new ArgumentOutOfRangeException(nameof(Amount), "Amount must be non-negative.");
        ArgumentException.ThrowIfNullOrWhiteSpace(Currency);
    }

    public Money Add(Money other)
    {
        if (Currency != other.Currency) throw new InvalidOperationException("Currency mismatch.");
        return this with { Amount = Amount + other.Amount };
    }
}

// Small stack-allocated value type
public readonly record struct Point(double X, double Y);

with expressions create modified copies — preserve immutability instead of mutating.

Pattern matching — eliminate casting and chains

// Switch expression (C# 8+)
string Describe(object obj) => obj switch
{
    int n when n > 0  => $"positive int: {n}",
    int n             => $"non-positive int: {n}",
    string s          => $"string of length {s.Length}",
    null              => "null",
    _                 => obj.GetType().Name,
};

// Property patterns (C# 8+)
string Category(Order order) => order switch
{
    { Total: > 1000, IsPriority: true } => "VIP",
    { Total: > 500 }                    => "Large",
    _                                   => "Standard",
};

// List patterns (C# 11+)
bool StartsWithOne(int[] nums) => nums is [1, ..];

Never cast ((T)obj) without a prior is check. Use type patterns (obj is T t) to combine the check and the cast.

Async/await — correct propagation

// Propagate CancellationToken everywhere
public async Task<User> GetUserAsync(int id, CancellationToken ct = default)
{
    var user = await _repository.FindAsync(id, ct);
    return user ?? throw new KeyNotFoundException($"User {id} not found.");
}

// ConfigureAwait(false) in library code (not in application code / controllers)
var data = await _client.GetStringAsync(url, ct).ConfigureAwait(false);

// Avoid async void — use async Task instead
// BAD:  public async void OnSomeEvent(...)
// GOOD: public async Task HandleAsync(...)

// Fire-and-forget requires explicit error handling
_ = Task.Run(async () =>
{
    try { await DoBackgroundWorkAsync(); }
    catch (Exception ex) { _logger.LogError(ex, "Background work failed"); }
});

Never use .Result or .Wait() on a Task — it risks deadlocks on synchronisation-context–bound runtimes (ASP.NET Core, WinForms).

IDisposable / IAsyncDisposable — resource cleanup

// Implement IDisposable when owning unmanaged resources or disposable children
public sealed class DatabaseConnection : IDisposable
{
    private readonly SqlConnection _connection;
    private bool _disposed;

    public DatabaseConnection(string connectionString)
        => _connection = new SqlConnection(connectionString);

    public void Dispose()
    {
        if (_disposed) return;
        _connection.Dispose();
        _disposed = true;
    }
}

// Prefer IAsyncDisposable for async cleanup (e.g., flushing async streams)
public sealed class FileWriter : IAsyncDisposable
{
    private readonly StreamWriter _writer;

    public async ValueTask DisposeAsync() => await _writer.DisposeAsync();
}

// Always use using declarations / using statements
await using var writer = new FileWriter(path);
using var conn = new DatabaseConnection(connStr);

Seal classes that implement IDisposable unless they are designed for inheritance. Add a protected virtual void Dispose(bool disposing) pattern only when the class is unsealed.

Naming conventions

SymbolConventionExample
Types, methods, properties, eventsPascalCaseOrderService, GetUserAsync
Private / protected fields_camelCase_repository, _logger
Local variables, parameterscamelCaseuserId, cancellationToken
Constants, static readonly fieldsPascalCaseMaxRetries, DefaultTimeout
InterfacesI prefix + PascalCaseIOrderRepository
Generic type parametersT or descriptive T-prefixT, TKey, TValue
Async methodsAsync suffixGetOrderAsync, SaveAsync
public interface IUserRepository
{
    Task<User?> FindAsync(int id, CancellationToken ct = default);
    Task<IReadOnlyList<User>> ListActiveAsync(CancellationToken ct = default);
}

public sealed class UserRepository : IUserRepository
{
    private readonly AppDbContext _db;

    public UserRepository(AppDbContext db) => _db = db;

    public async Task<User?> FindAsync(int id, CancellationToken ct = default)
        => await _db.Users.FindAsync(new object[] { id }, ct);

    public async Task<IReadOnlyList<User>> ListActiveAsync(CancellationToken ct = default)
        => await _db.Users.Where(u => u.IsActive).ToListAsync(ct);
}

File-scoped namespaces (C# 10+)

Prefer file-scoped namespaces to reduce indentation:

// Preferred (C# 10+)
namespace MyApp.Users;

public record User(int Id, string Email);

// Avoid for new code (block-scoped adds one level of indentation)
namespace MyApp.Users
{
    public record User(int Id, string Email);
}

var — local type inference

// Good — type is clear from the right-hand side
var users = new List<User>();
var order = await _orderRepo.FindAsync(id, ct);
var (first, rest) = GetParts();

// Avoid — type is not obvious
var result = Process(data);   // What type is result?

var is for local variables only. Never use for fields, parameters, or return types.

Class design rules

  • Prefer composition over inheritance for behaviour reuse; reserve inheritance for genuine is-a relationships.
  • Seal concrete classes that are not designed for extension (sealed class).
  • Keep constructors lean — no business logic; use factory methods or initialisation helpers for complex setup.
  • Minimise public API surface — internal by default, public only when the type/member is part of the contract.
  • No static mutable state — static fields holding mutable objects are a concurrency and testability hazard.
// Prefer static factory when construction can fail
public sealed class Email
{
    private readonly string _value;
    private Email(string value) => _value = value;

    public static Email Parse(string raw)
    {
        if (!raw.Contains('@')) throw new FormatException($"'{raw}' is not a valid email.");
        return new Email(raw.Trim().ToLowerInvariant());
    }

    public override string ToString() => _value;
}

レビュー

まだレビューはありません。使ってみた感想をお寄せください。

同じリポジトリのスキル

概要と使いどころ

Angular 18-21 project structure, standalone components vs NgModule, control flow (@if/@for/@switch + *ngIf/*ngFor legacy), decorators, dependency injection (inject() function), lifecycle hooks, pipes, Angular Universal SSR pointer. Use this skill to: - Detect project style (standalone vs NgModule) and apply matching patterns. - Pick correct decorators and DI approach. - Use modern control flow (@if/@for/@switch) in Angular 17+ projects. - Apply `inject()` function over constructor injection where appropriate. - Wire bootstrap correctly (bootstrapApplication for standalone, AppModule for legacy). Do NOT use this skill for: - State management (see angular-state-and-rx). - Routing (see angular-routing). - Forms (see angular-forms). - Testing (see angular-testing).

日本語の概要は準備中です。原文の説明を表示しています。

AratKruglik/claude-sdlc362026年9月21日 更新

Angular forms: Reactive Forms (preferred — typed FormGroup/FormControl since Angular 14, FormBuilder, custom + async validators, FormArray, multi-step) and Template-driven (`[(ngModel)]` + FormsModule). Validation strategies, server error mapping, accessibility. Use this skill to: - Build Reactive Forms with typed FormGroup/FormControl. - Use FormBuilder для concise syntax. - Implement custom synchronous and async validators. - Wire FormArray for dynamic field lists. - Map server errors back to form fields. - Pick Reactive vs Template-driven (prefer Reactive). Do NOT use this skill for: - General conventions (see angular-conventions). - State management beyond forms (see angular-state-and-rx). - Routing (see angular-routing). - Testing forms (see angular-testing).

日本語の概要は準備中です。原文の説明を表示しています。

AratKruglik/claude-sdlc362026年9月21日 更新

Angular Router (built-in `@angular/router`) — route configuration for standalone and NgModule projects, functional guards (Angular 14.1+), lazy loading, route resolvers, typed params via signals/observables, programmatic navigation, route data and meta. Use this skill to: - Configure routes (standalone-style or NgModule-style). - Use functional guards (canActivate as function, preferred over class-based in 17+). - Lazy-load components or feature modules. - Implement auth guards via route meta + functional guards. - Read params/queries via `inject(ActivatedRoute)` + signals or RxJS. Do NOT use this skill for: - General conventions (see angular-conventions). - State management (see angular-state-and-rx). - Forms (see angular-forms). - Testing routes (see angular-testing).

日本語の概要は準備中です。原文の説明を表示しています。

AratKruglik/claude-sdlc362026年9月21日 更新

State management for Angular 18-21: signals (signal/computed/effect), services-as-state, NgRx Store + Effects + Selectors, NgRx Component Store, NgRx Signals (newer signal-based store). RxJS essentials — operators, async pipe, takeUntilDestroyed, signal/observable interop. Use this skill to: - Pick the right state tool (signals / services / NgRx variant / vue-query equivalent). - Use signals correctly (signal/computed/effect — when each). - Build a Pinia-style service-as-state singleton. - Set up NgRx Store + Effects + Selectors. - Use RxJS without leaking subscriptions (async pipe, takeUntilDestroyed, Subject patterns). - Bridge signals ↔ observables via toSignal / toObservable. Do NOT use this skill for: - General Angular conventions (see angular-conventions). - Routing state (see angular-routing). - Form state (see angular-forms). - Testing state (see angular-testing).

日本語の概要は準備中です。原文の説明を表示しています。

AratKruglik/claude-sdlc362026年9月21日 更新

Testing Angular 18-21: TestBed, component harnesses (@angular/cdk/testing), Karma+Jasmine (default historical) vs Jest (jest-preset-angular, modern), Angular Testing Library (RTL-style). HttpClient mocking via HttpTestingController. NgRx Effects testing. Cypress / Playwright e2e. Use this skill to: - Detect runner (Karma+Jasmine vs Jest) and configure correctly. - Write component tests with TestBed. - Use component harnesses for Material / custom UI components. - Mock HttpClient via provideHttpClientTesting + HttpTestingController. - Test signal-based inputs with componentRef.setInput(). - Test NgRx Effects with provideMockActions. Do NOT use this skill for: - General Angular conventions (see angular-conventions). - Routing patterns broadly (see angular-routing — covers testing routes briefly). - Form patterns broadly (see angular-forms).

日本語の概要は準備中です。原文の説明を表示しています。

AratKruglik/claude-sdlc362026年9月21日 更新

Shared conventions for every SDLC development-phase architect agent: hard rules, code quality bar, workflow steps (superpowers invocation, spec reading, codebase exploration, verification), and the report/compact-summary contract. Architects load this skill first, then apply their stack-specific instructions on top.

日本語の概要は準備中です。原文の説明を表示しています。

AratKruglik/claude-sdlc362026年9月21日 更新

AratKruglik のスキルをすべて見る

このスキルの問題を報告する