Skip to content

Available C# and .NET features

James Groom edited this page Jul 1, 2024 · 14 revisions

This document is to supplement BizHawk development, and will be deleted once the project is on .NET Core. Yoshi will maintain a copy on GitLab for the benefit of other projects.

BizHawk-specific notes:

  • All projects in /src use C# 12 currently, as do all the .NET projects in /External*Projects but new features have only been adopted in the main solution.
  • In the added "convention" column, I've used required/disallowed/encouraged/discouraged/allowed like RFC 2119's MUST / MUST NOT / SHOULD / SHOULD NOT / MAY, respectively. Unsupported is also MUST NOT (because attempting to use the feature will result in an error).
  • Each project's target (Framework vs. Standard) is at the top of its project file, or you can check the project graph.

Legend:
✔️ Available
⭕ Available with polyfill
❌ Not available
? Unknown
Some features are marked ✔️ despite ostensibly needing a polyfill because they are enhancements to existing features and work anywhere the base feature is available—for example, switching on a Span<char> requires no additional polyfill.

🔵 Availability in .NET Framework 4.8 (net48)
🟢 Availability in .NET Standard 2.0 (netstandard2.0)
I have not considered Framework 4.7.2 and below as there is little reason not to upgrade to Framework 4.8. I have not considered Framework 4.8.1 because it matches Framework 4.8 in terms of language features and is generally not useful. I use Standard 2.0 and not Standard 2.1 as the latter is not subsumed by Framework 4.8, rendering it useless.

Note: .NET calls destructuring "deconstructing", not to be confused with destructing which .NET calls "finalising".

Feature 🔵 net48 🟢 ns2.0 convention for main BizHawk solution
[InlineArray] unsupported
using aliases for tuples, etc. ✔️ ✔️ disallowed (use a struct instead of a tuple alias and we'll consider replacing them with <Using/> later)
default arguments for lambdas ✔️ ✔️ discouraged (you should be using local methods)
ref readonly parameters ✔️ ✔️ allowed
unified Span/Array/List init syntax ✔️ ✔️ encouraged
primary constructors on non-records ✔️ ✔️ encouraged
^ C# 12 ^ 🔵 net48 🟢 ns2.0 ---
file access modifier ✔️ ✔️ allowed
simple ref fields unsupported
required props disallowed
Encoding.UTF8.GetBytes shorthand ✔️ ✔️ encouraged
enhanced nameof ✔️ ✔️ encouraged
pattern matching for Span<char> ✔️ ✔️ encouraged
Kotlin-like raw string literals ✔️ ✔️ allowed
list pattern matching ✔️ ✔️ encouraged
multi-line expressions in interpolated strings ✔️ ✔️ discouraged
generic maths using static abstract members unsupported
genericised attributes ✔️ ✔️ allowed
^ C# 11 ^ 🔵 net48 🟢 ns2.0 ---
per-method AsyncMethodBuilder ? ? unsupported
enhanced null analysis ✔️ ✔️ allowed
enhanced destructuring ✔️ ✔️ encouraged
sealed ToString in records ✔️ ✔️ discouraged (as per records)
limited string interpolation in consts ✔️ ✔️ encouraged
attributes for lambdas ✔️ ✔️ allowed
type inference for lambdas ✔️ ✔️ allowed
pattern matching IV ✔️ ✔️ encouraged
namespace A; ✔️ ✔️ disallowed (will mass-migrate)
global using A;/<Using> ✔️ ✔️ disallowed (will mass-migrate)
custom interpolated string handlers allowed
with for structs ✔️ ✔️ allowed
enhanced struct field init ✔️ ✔️ discouraged
record struct ✔️ ✔️ discouraged (as per records)
^ C# 10 ^ 🔵 net48 🟢 ns2.0 ---
enhanced partial methods ✔️ ✔️ allowed
[ModuleInitializer] method discouraged
attributes on local methods ✔️ ✔️ allowed
discarding lambda parameters ✔️ ✔️ encouraged
foreach picks up extension GetEnumerators ✔️ ✔️ discouraged (surely this can only be used for stupid)
covariant return type when overriding unsupported
enhanced type inference ✔️ ✔️ omit explicit type cast where possible, otherwise place the cast on default branch of switch and first branch of ternary
static lambdas ✔️ ✔️ encouraged
target-typed new() ✔️ ✔️ encouraged
basic function pointers ✔️ ✔️ encouraged
nint/nuint keywords ✔️ ✔️ allowed
pattern matching III ✔️ ✔️ encouraged
unindented Main ✔️ N/A N/A (neither executable uses it)
with for records ✔️ ✔️ discouraged (as per records)
init discouraged
record class ✔️ ✔️ discouraged
^ C# 9 ^ 🔵 net48 🟢 ns2.0 ---
@$"" (instead of $@"") ✔️ ✔️ disallowed
stackalloc as arg for Span param ✔️ ✔️ allowed
??= ✔️ ✔️ encouraged
Index and Range (^ and .. operators) discouraged (we also have a generic Range<T: unmanaged, IComparable<T>> but it has some problems, like not working with ..)
async streams unknown
NRTs (attribute-based analysis) encouraged
NRTs (syntax and basic analysis) ✔️ ✔️ encouraged for new files, see project graph for when #nullable enable is needed
static local methods ✔️ ✔️ encouraged
using statement without block ✔️ ✔️ encouraged
pattern matching II ✔️ ✔️ encouraged
switch expression ✔️ ✔️ encouraged
default interface methods unsupported
readonly methods/getters/setters ✔️ ✔️ encouraged
^ C# 8 ^ 🔵 net48 🟢 ns2.0 ---
field attribute target for auto-prop backing field ✔️ ✔️ discouraged (surely this can only be used for stupid)
stackalloc with array intialiser ✔️ ✔️ encouraged
^ C# 7.3 ^ 🔵 net48 🟢 ns2.0 ---
Span and co. encouraged
ref struct (stack-bound) ✔️ ✔️ allowed
readonly struct and in parameters ✔️ ✔️ encouraged
^ C# 7.2 ^ 🔵 net48 🟢 ns2.0 ---
inferred tuple field names ✔️ ✔️ discouraged
default without type ✔️ ✔️ encouraged for non-nullable value types, or as default! to appease compiler when definitely assigned, discouraged otherwise
async Main ? ? N/A (neither executable uses it)
^ C# 7.1 ^ 🔵 net48 🟢 ns2.0 ---
throw expression ✔️ ✔️ encouraged
enhanced int literals ✔️ ✔️ encouraged
discards ✔️ ✔️ encouraged
ref returns/locals ✔️ ✔️ allowed
expression-bodied constructors ✔️ ✔️ encouraged
local methods ✔️ ✔️ preferred over lambdas/delegates if used multiple times or to unsub from event
basic pattern matching ✔️ ✔️ encouraged
KeyValuePair<K, V>.Deconstruct allowed
basic tuples and destructuring ✔️ ✔️ encouraged
out var ✔️ ✔️ encouraged
^ C# 7.0 ^ 🔵 net48 🟢 ns2.0 ---
nameof ✔️ ✔️ encouraged
interpolated string literals ✔️ ✔️ preferred over string.Format or concatenation
null-conditional member access ✔️ ✔️ required
expression-bodied methods/props ✔️ ✔️ encouraged
inline initialisation of auto-props ✔️ ✔️ encouraged
catch (Exception e) when (predicate(e)) ✔️ ✔️ encouraged
using static A; ✔️ ✔️ allowed only for the file's own namespace
^ C# 6 ^ 🔵 net48 🟢 ns2.0 ---
[Caller*] ✔️ ✔️ allowed
async ✔️ ✔️ allowed
^ C# 5 ^ 🔵 net48 🟢 ns2.0 ---
covariant and contravariant generics ✔️ ✔️ encouraged
default arguments ✔️ ✔️ preferred over overloads
named arguments ✔️ ✔️ encouraged for primitive-typed parameters or when 2+ parameters are of the same type
dynamic type ✔️ discouraged
^ C# 4 ^ 🔵 net48 🟢 ns2.0 ---
object initialisation syntax ✔️ ✔️ encouraged
simple partial methods ✔️ ✔️ allowed
var ✔️ ✔️ preferred except when target-typed new() can be used
extension methods ✔️ ✔️ allowed
Expression trees ✔️ ✔️ discouraged (what are these even for)
lambdas ✔️ ✔️ preferred over delegates
LINQ's query expression syntax ✔️ ✔️ disallowed
anonymous classes ✔️ ✔️ disallowed (use tuples)
auto-props ✔️ ✔️ encouraged
^ C# 3 ^ 🔵 net48 🟢 ns2.0 ---
anonymous delegates ✔️ ✔️ disallowed
^ C# 2 ^ 🔵 net48 🟢 ns2.0 ---
using alias ✔️ ✔️ discouraged unless there's a conflict
delegate constructors ✔️ ✔️ disallowed
unsafe (pointers etc.) ✔️ ✔️ discouraged