Skip to content

C# and .NET docs supplement

James Groom edited this page Dec 28, 2023 · 45 revisions

To save us repeating our complaints about the lack of proper documentation under each section, let's agree to gather all the frustration here:

AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA

Contribute to the official docs if possible.

Const (byte/primitive) arrays

Not allowed as either array nor Span, despite string literals now effectively having the type const ReadOnlySpan<char>, and despite arrays of primitive types being allowed for attribute parameters since forever. Use static readonly and weep.

Const structs

Not allowed, even if they meet the criteria for unmanaged types and are littered with explicit layout attributes. First-class'd structs are no different, so fields of type ValueTuple, Range, and as noted above, Span cannot be const. Also ref structs, which makes slightly more sense. That's probably only because you'd be able to get a reference to a ref struct on the heap, something which shouldn't exist, by using reflection.

Deceptive collection type names

IReadOnly{Collection,Dictionary,List,Set} are for getting read-only views of the collections that implement them. They do not mean the collection is immutable (there are separate classes for that). The same goes for ReadOnlySpan.

Kotlin got this right by calling its interfaces e.g. List/MutableList instead of IReadOnlyList/IList. (And it also fixed the inheritance hierarchy.)

Featureset is determined by language level AND target

see feature matrix page

Guarded default in switch statements

You can only have 1 default branch, but case _ when ...: doesn't work. However, case var _ when ...: does.

MSBuild Condition placement

On (older versions of?) VS, Condition is ignored if placed on a property/item. Create a new <PropertyGroup/>.

MSBuild property evaluation execution order

When <Import/>ing a .props file, ProjectDir is unset (but SolutionDir is set, if applicable). Use MSBuildProjectDirectory. Note that the latter doesn't include a trailing slash.

NuGet resources are all for old CLI

up-to-date docs on MSDN

dotnet list BizHawk.sln package --outdated will list outdated <PackageReference/>s

NUL-terminated strings

.NET will happily include NUL ((char) 0) in a string if you use String..ctor(char[]). WinForms' Label.Text stops reading at the first NUL for measurement/rendering, at least under Mono.

Preprocessor TFM constants and .NET Standard

The table here is good for reference, but mind the note hidden at the bottom:

The NETSTANDARD<x>_<y>_OR_GREATER symbols are only defined for .NET Standard targets, and not for targets that implement .NET Standard [...]

That is, you must use #if !(NETSTANDARD2_1_OR_GREATER || NETCOREAPP2_1_OR_GREATER) and not just #if !NETSTANDARD2_1_OR_GREATER.

String.GetHashCode stability

The GetHashCode implementation for strings does not reflect the string's contents, and as such, the hash not stable between program instances.

It seems that Guid's implementation is stable across instances, and even across Mono and .NET 6+ implementations. It also gives 0 for Guid.Empty which is nice.

System.Drawing.Color.* rendered

Docs for Color don't include any pictures, so here's a nice chart.

System.Drawing.SystemIcons rendered

Docs for SystemIcons don't include any pictures, so here they are (Win10, Mono 6.12.x):

SystemIcons_Win10 SystemIcons_Mono

Notice also the default window icon (Form.Icon): on Windows, it's a distinct icon; on Mono (not shown in the screenshot), it resembles SystemIcon.Application. From 2.9, EmuHawk overrides the default to the logo.

Type casting

There are two types of casts in C#: the C-style (T) o throws if the object is not of the desired type, whereas o as T evaluates to null if it's not of the desired type. There's no '?' in this null-producing operator (this is probably only confusing if you use Kotlin).

If an object being the wrong type is exceptional—the method can't handle it gracefully—then throw a type cast exception straight away. Having it reported as an NRE when there's no null in sight just frustrates debugging efforts.

Type constraints (where clauses)

class in where clauses does not mean "not abstract", it means "reference type". Similarly, struct means "value type". There's a lot of complexity re: nullability, so check the docs if you're writing a generic method.

TODO euler diagram

WinForms Control.ResumeLayout footgun

// works
groupBox.ResumeLayout(performLayout: false);
groupBox.PerformLayout();
// breaks subtly
groupBox.ResumeLayout(performLayout: true);