Dev CoP Friday, Nov. 13, 2020
- Top Level Statements
record
typeinit
set statement- Pattern matching enhancements
- Performance and interop
- Targeted
new()
expressions static
anonymous functions- Covariant return types
- Target-typed new expressions
- Extension
GetEnumerator
support for foreach loops - Attributes on local methods
- Module initializers
- Partial method extension
- Citations
- Resources
Programs can now be as simple as:
System.Console.WriteLine("HelloWorld!");
Using Statements may also be built inline:
using System;
Console.WriteLine("HelloWorld!");
Inline functions can be top level statements:
void Print(string something) => System.Console.WriteLine($"Something: {something}");
Print("Else");
This opens up a lot of possibilities for scripting and functional style programming.
However, we probably want to avoid this for "normal" projects.
-Preston
[Records] are defined not by their identity, but by their contents. -Mads Torgersen
Characteristics:
- Immutable
- Extra built in compile time additions
IEquatable<t>
.Equals()
.GetHashCode()
.Clone()
override .ToString()
Inheritance is available:
public record Automobile {
public int wheels { get; init; }
public int doors { get; init; }
}
public record Motorcycle : automobile {
public Motorcycle() {
this.wheels = 2;
this.doors = 0;
}
}
"Init only setters provide consistent syntax to initialize members of an object. Property initializers make it clear which value is setting which property. The downside is that those properties must be settable."[1]
var tomAndJerry = new Cartoon() { Name = "Tom and Jerry", Year = 1940 };
//Compile/linting time error
tomAndJerry.Year = 1930;
public class Cartoon {
public string Name { get; init; }
public int Year { get; init; }
}
C# 8 introduced pattern matching in the switch statement. C# 9 is introducing some enhancements with and
, is
and or
operators.
var today = new Weather(DateTime.Now, 30);
var forecast = new Func<Weather, string>(
(_today) => _today.temperature switch
{
< 32 => "cold",
>= 32 and < 60 => "mild",
>= 60 and < 85 => "pleasant",
>= 85 => "hot"
});
Console.WriteLine($"Today will be {forecast(today)}");
public record Weather(DateTime date, int temperature);
Pattern matching can also be applied to if
statements.
var today = new Weather(DateTime.Now, 90, 80);
if (today is { temperature: >= 85, barometer: >= 60 })
Console.WriteLine($"Today will be hot & muggy.");
public record Weather(DateTime date, int temperature, int barometer
Underscores can now be used as a discard operator in Lambda functions.
"Password".Select (_ => "*")
The not
pattern is now available. Prior the !
operator was needed for negation boolean checking.
//Before
if(!("Hi There!" is string))
System.Console.WriteLine("Not a string");
//After
if("Hi There!" is not string)
System.Console.WriteLine("Not a string");
-
Native sized integers
Native sized integers define properties for MaxValue or MinValue. These values can't be expressed as compile time constants because they depend on the native size of an integer on the target machine.
-
Function pointers
Invoking the
delegate*
type usescalli
, in contrast to a delegate that usescallvirt
on theInvoke()
method. Syntactically, the invocations are identical. -
Omitting the locals
init
flag...you can add the
System.Runtime.CompilerServices.SkipLocalsInitAttribute
to instruct the compiler not to emit thelocalsinit
flag. This flag instructs the CLR to zero-initialize all local variables...You may add it to a single method or property, or to a class, struct, interface, or even a module.
Weather today = new(DateTime.Now, 80);
Console.WriteLine($"Today: {today}");
public record Weather(DateTime date, int temperature);
Something toPrint = new() { print = "else." };
Console.WriteLine($"Something {toPrint}");
public record Something {
public string print { get; init; }
}
Starting in C# 9.0, you can add the
static
modifier to lambda expressions or anonymous methods. Static lambda expressions are analogous to thestatic
local functions: astatic
lambda or anonymous method can't capture local variables or instance state. Thestatic
modifier prevents accidentally capturing other variables.
var phrase = "How now brown cow?";
var now = DateTime.Now;
phrase
.Split()
.Select(static p => p);
//Won't Compile, because static anonymous methods can't reference a local variable
//phrase
// .Split()
// .Select(static p => $"{now.ToShortDateString()}: {p}");
Covariant return types provide flexibility for the return types of override methods. An override method can return a type derived from the return type of the overridden base method. This can be useful for records and for other types that support virtual clone or factory methods.
Sedan HondaCivic = new();
var myCivic = HondaCivic.Clone();
//Output: My Civic is a Sedan
Console.WriteLine($"My Civic is a {myCivic.GetType()}");
//Declarations
public class Automobile {
public virtual Automobile Clone() => new Automobile();
}
public class Car : Automobile
{
public override Car Clone() => new Car();
}
public class Sedan : Car
{
public override Sedan Clone() => new Sedan();
}
Weather today;
today = new(DateTime.Now, 100);
Console.WriteLine($"Todays's {today.GetType()} is {today.temperature} Degrees!");
//Output:
//Todays's Weather is 100 Degrees!
//Declartions
public record Weather(DateTime date, int temperature);
But this feature is particulary useful when creating object in-line for function calls.
void Main()
{
Forecast(new(DateTime.Now, 100));
//Output:
//Todays's Weather is 100 Degrees!
}
//Declartions
public record Weather(DateTime date, int temperature);
public void Forecast(Weather today)
{
Console.WriteLine($"Todays's {today.GetType()} is {today.temperature} Degrees!");
}
The foreach
loop can now be used on with values or objects that have a GetEnumerator
extension method.
foreach (char l in "HelloWorld!")
Console.WriteLine(l);
static class Extensions
{
public static IEnumerator<char> GetEnumerator(this string x) =>
x.Split().Cast<char>().GetEnumerator();
}
In practice, this change means you can add
foreach
support to any type. You should limit its use to when enumerating an object makes sense in your design.
Attributes can now be added to local methods. This allows for compiler processing changes and optimzations at the method level. An example of this would be marking up a method as nullable.
void Main()
{
Console.WriteLine($"Today's forecast is {TodaysForecast(new(DateTime.Now, 45))}");
}
public record Weather(DateTime date, int temperature);
//Creating the Nullable attribute for later optimizations
class Nullable : Attribute { }
[Nullable]
public string TodaysForecast(Weather today)
=> today.temperature switch
{
< 32 => "freezing",
> 32 and <= 50 => "cold",
> 50 and <= 90 => "warm",
> 90 => null, //Words can just not describe this feeling;
};
... Module initializers are methods that have the ModuleInitializerAttribute attribute attached to them. These methods will be called by the runtime before any other field access or method invocation within the entire module. A module initializer method:
- Must be static
- Must be parameterless
- Must return void
- Must not be a generic method
- Must not be contained in a generic class
- Must be accessible from the containing module [Must at least be
internal
orpublic
]
This sets an order of precedence for operations at runtime. This is similar to the order of events for the lifetime of an object in some frameworks like Windows Forms or React.
Partial methods allow declaration of a method in one partial
class and its implementaion in a later defined partial
class
var kitty = new Animal();
Console.WriteLine($"{kitty.Roar()}!");
public partial class Animal {
public partial string Roar();
}
public partial class Animal {
public partial string Roar() => "Raaaaar";
}
This can be useful when creating the template for expected operations, but leaving the implementation to a code-generator or framework tool later on.
- 1: LINQPad Samples: What's New in C# 9
- 2: What's new in C# 9.0
- 3: An Introduction to the New Features in C# 9
- 4: Reserved attributes contribute to the compiler's null state static analysis