C# and .NET coding standards for generating new code files. Use when creating new classes, methods, controls, tests, or any new C# / VB source files in this repository. Covers C# 14 / .NET 10 patterns, naming, formatting, XML docs, WinForms conventions, and performance idioms.
These rules apply whenever you generate new C# or VB.NET source files in
this repository. For modernizing or refactoring existing files, see the
code-modernization skill instead.
net10.0).eng/common/..editorconfig.Use C# 14 features and patterns. Key features to prefer:
| Feature | Example |
|---|---|
| Extension members (extension blocks) | extension(string s) { public bool IsBlank() => ... } |
field keyword in properties | set => field = value ?? throw ...; |
| Null-conditional assignment | customer?.Order = GetOrder(); |
nameof on unbound generics | nameof(List<>) |
| Lambda modifiers without types | (text, out result) => int.TryParse(text, out result) |
| Partial constructors / events | partial MyClass() { } |
First-class Span<T> conversions | Implicit T[] → Span<T> / ReadOnlySpan<T> |
using directives unless the import is non-obvious.var PolicyApply the following rules in priority order:
var for primitive types. Always spell out int, string,
bool, double, float, decimal, char, byte, long, etc.// REQUIRED — explicit types for primitives
int count = 5;
string name = "Button";
bool isVisible = true;
Use var when the type is already visible or clearly implied on the
same line. Repeating the type degrades readability. This applies to:
var foo = (SomeType) bar;as casts: var button = item as ToolStripDropDownButton;<T> already
tells the reader what they are getting back:
var host = this.GetService<IDesignerHost>();
var session = provider.GetRequiredService<DesignerSession>();out var in generic methods that name the type:
site.TryGetService<INestedContainer>(out var container) — the <T>
already specifies the type.var is
preferred because the type is already readable from the call itself:
var componentType = component.GetType();
var resourceStream = BitmapSelector.GetResourceStream(type, name);
TryLoadBitmapFromStream(stream, out var resourceBitmap)// GOOD — var when type is visible or implied on the line
var designerHostShim = (IDesignerHostShim)designerHost;
var host = this.GetService<IDesignerHost>();
var manager = host.GetExport<ViewModelClientFactoryManager>();
var componentType = component.GetType();
var resourceStream = BitmapSelector.GetResourceStream(componentType, componentType.Name + ".bmp");
service.TryGetValue<AppSettings>(out var settings);
var for deeply nested or complex generic types where the full type
name is unwieldy:using var pooledList = ListPool<IComponent>.GetPooledObject();
var result = pooledList.Object;
var (key, value) = dictionary.First(); // tuple deconstruction
// REQUIRED — explicit when type is not obvious
CreateViewModelResponse response = session.GetWinFormsEndpoints().DocumentOutline.CreateViewModel(session.Id);
TreeViewHitTestInfo hitTestInfo = _treeView.HitTest(e.Location);
new() over var when the type is visible on the
left — clean construction without redundancy:// Before
Dictionary<string, List<int>> map = new Dictionary<string, List<int>>();
var map = new Dictionary<string, List<int>>();
// After
Dictionary<string, List<int>> map = new();
Button saveButton = new();
Do NOT use target-typed new() when the type isn't visible on the same line:
// DO — type is visible on the right, so var is fine:
var map = new Dictionary<string, List<int>>();
// DON'T — _map is a backing field declared elsewhere,
// so the type isn't visible here. Too implicit.
_map = new();
var is always fine for tuple deconstruction:var (nodes, images) = viewModel.UpdateTreeView(displayStyle);
var (key, value) = dictionary.First();
// Before
List<string> items = new List<string>();
// After
List<string> items = [];
// Before
Control[] controls = _view.Controls.Cast<Control>().ToArray();
// After
Control[] controls = [.. _view.Controls.Cast<Control>()];
// Be careful! Will not compile, we need a constructable array type in this
// context, so collection initializer syntax is not possible here.
Control CreateErrorControlForMessage(string message)
=> CreateErrorControl([new InvalidOperationException(message)]);
// In this case we need:
Control CreateErrorControlForMessage(string message)
=> CreateErrorControl(new[] { new InvalidOperationException(message) });
is null / is not null — never == null or != null.value ?? "default", value ??= GetDefault().| Element | Convention |
|---|---|
private / internal instance fields | _camelCase |
private static fields | s_camelCase |
[ThreadStatic] fields | t_camelCase |
| Constants | PascalCase |
| All other members | PascalCase |
this. unless strictly necessary to resolve ambiguity.private over internal over
public.static when they do not access instance state.Use expression bodies (=>) for single-expression methods and read-only
properties. When the total line length would exceed 60 characters, wrap by
placing the => on the next line, indented:
// Short — fits on one line
internal int BorderWidth => _borderWidth;
// Long — wrap the arrow to the next line
private bool IsValidSize(Size size)
=> size.Width > 0 && size.Height > 0;
internal string QualifiedName
=> $"{Namespace}.{TypeName}";
Important semantic distinction:
public Foo Bar => new Foo();creates a new instance on every access.public Foo Bar { get; } = new Foo();creates one instance at construction. Never convert between these forms unless the original semantics were provably wrong.
return statements.Put each branch on its own line unless the whole expression is very short:
Color textColor = e.Item.Enabled
? GetDarkModeColor(e.TextColor)
: GetDarkModeColor(SystemColors.GrayText);
Prefer switch expressions over switch statements over if-chains:
string result = value switch
{
> 0 => "Positive",
< 0 => "Negative",
_ => "Zero"
};
Use and, or, relational, property, tuple, type, and list patterns where they
improve clarity. When converting if-chains that return or assign, a switch
expression is almost always clearer.
Prefer extension method call syntax over static helpers when the same operation is available in both forms — extensions read more naturally and reduce visual noise:
// Prefer
Size deviceSize = image.Size.LogicalToDeviceUnits();
// Over
Size deviceSize = DpiHelper.LogicalToDeviceUnits(image.Size);
#pragma or [SuppressMessage]Use inline #pragma or [SuppressMessage] at the call site over global suppressions, so justification is visible in context.
Use named arguments when passing multiple literals or when the meaning of a parameter isn't clear from the argument expression itself:
// GOOD — named arguments clarify meaning of literals
var errorControl = CreateErrorControlForMessage(
message: "An unexpected error occurred. Please try again.",
showRetryButton: true);
Note: When method calls take a lot of space due to a long argument list, consider wrapping the individual arguments on separate lines. If you then decide to use named arguments, use them for every argument to improve readability and consistency:
LongMethodWithManyNamedArguments(
firstArgument: value1,
secondArgument: value2,
thirdArgument: value3,
fourthArgument: value4);
// Fine — 2 or fewer:
var names = items.Where(x => x.IsActive).ToList();
// Wrap — more than 2:
var results = collection
.Where(x => x.IsActive)
.OrderBy(x => x.Name)
.Select(x => x.Id)
.ToList();
Use throw-helper methods — never hand-roll null / range checks:
ArgumentNullException.ThrowIfNull(parameter);
ArgumentOutOfRangeException.ThrowIfNegative(value);
ObjectDisposedException.ThrowIf(_disposed, this);
Always use nameof() for parameter and member names in exceptions and asserts.
char overloads: text.Contains('c'), sb.Append('x').ReadOnlySpan<char> over Substring when possible.items.Any() over Count() > 0.TryGetValue over ContainsKey + indexer.string.IsNullOrEmpty over ?.Length == 0.Array.Empty<T>() over new T[0].using declarations (using var ...) over using blocks. Use the
block form only when a tighter scope is required.using Pen focusPen = new(focusColor)
{
Width = 1.0f,
DashStyle = DashStyle.Dot
};
Replace/avoid hard-coded literals with named constants or enums for new code when possible. If a literal is truly self-explanatory and unlikely to change, it may be acceptable to leave it as-is.
## XML Documentation
Every class, struct, record, interface, and enum — **regardless of access
modifier, including `private` nested types** — must have an XML doc header:
* **`<summary>`** — always present; a concise statement of the type's purpose.
* **`<remarks>`** — add for non-trivial types. Use `<para>` for multiple
paragraphs.
* Use `<inheritdoc/>` on overridden members.
* Do **not** XML-comment local functions; use a regular `//` comment if
the purpose is not self-evident.
* Use **Unicode characters** in docs — never HTML entities.
* Indent XML structure with **1 space** per nesting level:
```csharp
/// <summary>
/// Gets or sets the border width of the control.
/// </summary>
/// <remarks>
/// <para>
/// The border width affects the visual appearance and layout
/// calculations of the control.
/// </para>
/// <para>
/// Values must be non-negative. A value of 0 means no border.
/// </para>
/// </remarks>
public int BorderWidth { get; set; }
public event EventHandler<EventArgs>? Click;
protected virtual void OnClick(EventArgs e)
{
Click?.Invoke(this, e);
}
// NRT-aware handler signature
private void OnSomeEvent(object? sender, EventArgs e) { }
Use EventArgs.Empty for parameterless event raises.
private Button? _okButton;is not null:protected override void Dispose(bool disposing)
{
if (disposing && components is not null)
{
components.Dispose();
}
base.Dispose(disposing);
}
[SupportedOSPlatform("windows")]
private void WindowsSpecificMethod() { }