C# 9 Early Review
Already Implemented
1. Pattern matching improvements
1.1 Type Patterns
void M(object o1, object o2)
{
var t = (o1, o2);
if (t is (int, string)) {} // test if o1 is an int and o2 is a string
switch (o1) {
case int: break; // test if o1 is an int
case System.String: break; // test if o1 is a string
}
}
1.2 Parenthesized Patterns
Parenthesized patterns permit the programmer to put parentheses around any pattern. This is not so useful with the existing patterns in C# 8.0, however the new pattern combinators introduce a precedence that the programmer may want to override.
primary_pattern
: parenthesized_pattern
;
parenthesized_pattern
: '(' pattern ')'
;
1.3 Relational Patterns
Relational patterns permit the programmer to express that an input value must satisfy a relational constraint when compared to a constant value:
public static LifeStage LifeStageAtAge(int age) => age switch
{
< 0 => LiftStage.Prenatal,
< 2 => LifeStage.Infant,
< 4 => LifeStage.Toddler,
< 6 => LifeStage.EarlyChild,
< 12 => LifeStage.MiddleChild,
< 20 => LifeStage.Adolescent,
< 40 => LifeStage.EarlyAdult,
< 65 => LifeStage.MiddleAdult,
_ => LifeStage.LateAdult,
};
1.4 Pattern Combinators
Three new pattern forms
- pattern
and
pattern - pattern
or
pattern not
pattern
//example 1
bool IsLetter(char c) => c is (>= 'a' and <= 'z') or (>= 'A' and <= 'Z');//example 2
switch (o)
{
case 1 or 2:
case Point(0, 0) or null:
case Point(var x, var y) and var p:
}
2. Target-typed new
Do not require type specification for constructors when the type is known. Allow field initialization without duplicating the type.
Dictionary<string, List<int>> field = new() {
{ "item1", new() { 1, 2, 3 } }
};
Allow omitting the type when it can be inferred from usage.
XmlReader.Create(reader, new() { IgnoreWhitespace = true });
Instantiate an object without spelling out the type.
private readonly static object s_syncObj = new();
3. Lambda discard parameters
Unused parameters do not need to be named. The intent of discards is clear, i.e. they are unused/discarded.
Allow discards (_
) to be used as parameters of lambdas and anonymous methods. For example:
- lambdas:
(_, _) => 0
,(int _, int _) => 0
- anonymous methods:
delegate(int _, int _) { return 0; }
4. Attributes on local functions
Now attributes can be part of the declaration of a local function.
class C
{
void M()
{
int x;
local1();
Console.WriteLine(x);
[Conditional("DEBUG")]
void local1()
{
x = 42;
}
}
}
5. Native ints
The identifiers nint
and nuint
are new contextual keywords that represent native signed and unsigned integer types. The identifiers are only treated as keywords when name lookup does not find a viable result at that program location.
nint x = 3;
string y = nameof(nuint);
_ = nint.Equals(x, 3);
The types nint
and nuint
are represented by the underlying types System.IntPtr
and System.UIntPtr
with compiler surfacing additional conversions and operations for those types as native ints.
6. Extending Partial
The language will change to allow partial
methods to be annotated with an explicit accessibility modifier. This means they can be labeled as private
, public
, etc ...
When a partial
method has an explicit accessibility modifier though the language will require that the declaration has a matching definition even when the accessibility is private
:
partial class C
{
// Okay because no definition is required here
partial void M1();// Okay because M2 has a definition
private partial void M2();// Error: partial method M3 must have a definition
private partial void M3();
}partial class C
{
private partial void M2() { }
}
Further the language will remove all restrictions on what can appear on a partial
method which has an explicit accessibility. Such declarations can contain non-void return types, ref
or out
parameters, extern
modifier, etc ... These signatures will have the full expressivity of the C# language.
partial class D
{
// Okay
internal partial bool TryParse(string s, out int i);
}partial class D
{
internal partial bool TryParse(string s, out int i) { }
}
This explicitly allows for partial
methods to participate in overrides
and interface
implementations:
interface IStudent
{
string GetName();
}partial class C : IStudent
{
public virtual partial string GetName();
}partial class C
{
public virtual partial string GetName() => "Jarde";
}
7. Function pointers
This feature provides language constructs that expose low level IL opcodes that cannot currently be accessed efficiently, or at all: ldftn
, ldvirtftn
, ldtoken
and calli
. These low level opcodes can be important in high performance code and developers need an efficient way to access them.
8. Skip locals init
Allow suppressing emit of localsinit
flag via SkipLocalsInitAttribute
attribute.
In progress
1. Records
Init-only properties are great if you want to make individual properties immutable. If you want the whole object to be immutable and behave like a value, then you should consider declaring it as a record:
public data class Person
{
public string FirstName { get; init; }
public string LastName { get; init; }
}
The data
keyword on the class declaration marks it as a record. This imbues it with several additional value-like behaviors, which we’ll dig into in the following. Generally speaking, records are meant to be seen more as “values” – data! – and less as objects. They aren’t meant to have mutable encapsulated state. Instead you represent change over time by creating new records representing the new state. They are defined not by their identity, but by their contents.
To help with this style of programming, records allow for a new kind of expression; the with
-expression:
var otherPerson = person with { LastName = "Hanselman" };
Short declaration:
public data class Person { string FirstName; string LastName; }//Means exactly the same as the one we had before:public data class Person
{
public string FirstName { get; init; }
public string LastName { get; init; }
}
Inheritance:
public data class Person { string FirstName; string LastName; }
public data class Student : Person { int ID; }
2. Parameter null-checking
This allows for standard null
validation on parameters to be simplified using a small annotation on parameters:
// Before
void Insert(string s) {
if (s is null)
throw new ArgumentNullException(nameof(s)); ...
}// After
void Insert(string s!) {
...
}
3. Covariant Returns
Support covariant return types. Specifically, permit the override of a method to return a more derived return type than the method it overrides, and similarly to permit the override of a read-only property to return a more derived return type. Callers of the method or property would statically receive the more refined return type from an invocation, and overrides appearing in more derived types would be required to provide a return type at least as specific as that appearing in overrides in its base types.
Example:
class Compilation ...
{
virtual Compilation WithOptions(Options options)...
}class CSharpCompilation : Compilation
{
override CSharpCompilation WithOptions(Options options)...
}
4. Static lambdas
To avoid accidentally capturing any local state when supplying lambda functions for method arguments, prefix the lambda declaration with the static keyword. This makes the lambda function just like a static method, no capturing locals and no access to this or base.
int y = 10;
someMethod(x => x + y); // captures 'y', causing unintended allocation.
with this proposal you could the static keyword to make this an error.
int y = 10;
someMethod(static x => x + y); // error!const int y = 10;
someMethod(static x => x + y); // okay :-)
5. Top-level statements
Allow a sequence of statements to occur right before the namespace_member_declarations of a compilation_unit (i.e. source file).
Examples:
// Example #1
await System.Threading.Tasks.Task.Delay(1000);
System.Console.WriteLine("Hi!");// would be converted tostatic class $Program
{
static async Task $Main(string[] args)
{
await System.Threading.Tasks.Task.Delay(1000);
System.Console.WriteLine("Hi!");
}
}--------------------------------------------------------------------// Example #2
await System.Threading.Tasks.Task.Delay(1000);
System.Console.WriteLine("Hi!");
return 0;// would be converted tostatic class $Program
{
static async Task<int> $Main(string[] args)
{
await System.Threading.Tasks.Task.Delay(1000);
System.Console.WriteLine("Hi!");
return 0;
}
}--------------------------------------------------------------------// Example #3
System.Console.WriteLine("Hi!");
return 2;// would be converted tostatic class $Program
{
static int $Main(string[] args)
{
System.Console.WriteLine("Hi!");
return 2;
}
}
6. Target-typed conditional
For a conditional expression c ? e1 : e2
, when
- there is no common type for
e1
ande2
, or - for which a common type exists but one of the expressions
e1
ore2
has no implicit conversion to that type
a new implicit conditional expression conversion is defined that permits an implicit conversion from the conditional expression to any type T
for which there is a conversion-from-expression from e1
to T
and also from e2
to T
. It is an error if a conditional expression neither has a common type between e1
and e2
nor is subject to a conditional expression conversion.
7. Extension GetEnumerator
Allow foreach
loops to recognize an extension method GetEnumerator
method that otherwise satisfies the foreach
pattern, and loop over the expression when it would otherwise be an error.
8. Relax ordering of ref
and partial
modifiers
Currently, partial
must appear directly before struct
, class
, or another type declaration keyword. If the type is a ref
struct, ref
must appear immediately before partial
or struct
. It seems likely that various other keywords could be used to disambiguate these contextual modifiers and allow us to relax the constraints on where partial
and ref
can appear in the modifier list.
9. Module initializers
- Enable libraries to do eager, one-time initialization when loaded, with minimal overhead and without the user needing to explicitly call anything
- One particular pain point of current
static
constructor approaches is that the runtime must do additional checks on usage of a type with a static constructor, in order to decide whether the static constructor needs to be run or not. This adds measurable overhead. - Enable source generators to run some global initialization logic without the user needing to explicitly call anything
P.S. Here you can read more about all new features https://github.com/dotnet/roslyn/blob/master/docs/Language%20Feature%20Status.md