Live demo — data resets daily at 03:00 UTC. Nothing you enter is saved. Server UI →

C# Coding Standards

Source: Documentation/source/CLAUDE.md These are the enforced conventions for all AccordionQ2 C# code.


Project Context

AccordionQ2 is a C# .NET embedded application running on Raspberry Pi (ARM32/Linux). It exposes hardware instruments and industrial protocols (SCPI, Modbus, PMBus, HCI, etc.) to remote clients over a network protocol server.


Null Handling

// Constructor parameters — throw immediately on null
public MyClass(IFoo foo)
{
    _foo = foo ?? throw new ArgumentNullException(nameof(foo));
}

// Optional/nullable members — safe invocation
proxy?.Stop();

// Null-coalescing before instance methods — never call directly on nullable string
var trimmed = (value ?? "").Trim();

// Prefer is null / is not null over == null
if (value is null) ...
if (result is not null) ...

Exception Handling

try
{
    await DoWorkAsync(ct);
}
catch (OperationCanceledException)   // ALWAYS catch separately, ALWAYS rethrow
{
    throw;
}
catch (Exception ex)
{
    Logger.Error(log4net, ex);        // log via IFileLogging wrapper
    throw;                            // use throw; not throw ex;
}

// Cleanup / dispose — use bare catch only here
try { resource.Dispose(); } catch { /* best effort */ }

Logging

Every class declares a static logger:

private static readonly log4net.ILog log4net =
    LogHelper.GetLogger(MethodBase.GetCurrentMethod().DeclaringType);

Always call through the IFileLogging wrapper — never call log4net.Info(...) directly:

Logger.Info(log4net, "Module loaded");
Logger.Debug(log4net, $"Processing item {id}");
Logger.Warn(log4net, "Config value missing, using default");
Logger.Error(log4net, ex);
Level Use for
Info Operational events (start, stop, load, connect)
Debug Per-operation detail, verbose tracing
Warn Configuration issues, recoverable problems
Error All caught exceptions

Async

// All async methods return Task or Task<T>
public async Task<string> GetValueAsync(CancellationToken ct = default) { ... }

// Never async void
// ✗ public async void OnEvent() { ... }
// ✓ public async Task OnEventAsync() { ... }

// Always accept and thread through CancellationToken
public async Task ProcessAsync(CancellationToken ct)
{
    await _client.DoAsync(ct).ConfigureAwait(false);
}

// Suffix all async methods with Async
public Task StartAsync(CancellationToken ct) { ... }

// Fire-and-forget: discard explicitly
_ = Task.Run(() => BackgroundWork());

Naming

Element Convention Example
Classes, methods, properties, events PascalCase ModuleManager, GetValueAsync
Private fields _camelCase _httpClient, _channelMap
Local variables camelCase moduleCount, statusMessage
Boolean methods Is* / Has* prefix IsConnected(), HasErrors()
Abbreviations Preserve casing HAL, Uart, SCPI, mDNS

General Style

// var for non-obvious types; explicit for primitives and when clarity matters
var modules = await client.GetAllAsync();
int count = modules.Count;

// String interpolation preferred
var msg = $"Loaded {count} modules in {elapsed.TotalMs:F1} ms";

// LINQ preferred over loops when readable
var names = modules.Where(m => m.Enabled).Select(m => m.Name).ToList();

// private readonly for all non-mutating fields
private readonly HttpClient _http;
private readonly string _baseUrl;

// One class per file
// #region to group logical sections in large classes

Patterns to Avoid

Anti-pattern Why
Mutable statics Thread safety; loggers are the only static readonly exception
record types Not used in this codebase
Global usings Explicit usings only
Reflection (except logger init and module loading) Complexity, performance
async void Unobservable exceptions
throw ex; Destroys stack trace — always use throw;
Calling instance methods on potentially-null strings Use (s ?? "").Trim() pattern
An unhandled error has occurred. Reload 🗙

Rejoining the server...

Rejoin failed... trying again in seconds.

Failed to rejoin.
Please retry or reload the page.

The session has been paused by the server.

Failed to resume the session.
Please retry or reload the page.