As of this writing, C# 8 is still in Preview 5, so It’s not 100% clear if all listed features will end up in final release. C# 8 will be probably released at .NET Conf , September 23 — 25, 2019.
Nevertheless, let’s take a look at new features of C# 8.
List of reported new features of C# 8 – Preview 5 (you can find official list of new features here):
- Readonly members
- Default interface members
- More patterns in more places
- Switch expressions
- Property expressions
- Tuple patterns
- Positional patterns
- using declarations
- Static local functions
- Disposable ref structs
- Nullable reference types
- Asynchronous streams
- Indices and ranges
- Null-coalescing assignment
- Unmanaged constructed types
- Enhancement of interpolated verbatim strings
Let’s quickly check some of these new features with my comments on each of the new feature:
Default interface members
In C#8, interfaces can have default implementation. As a result, developer is not obligatory to implement this members in implementation classes. Let’s take a look:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 |
namespace Jenx.Cs8NewFeatures.DefaultImplementationsInterfaces { public interface IAgeable { void SetAge(int age); // new, default method inplementation in interface void SetAgeDefaultImplementation(int age) { Console.WriteLine($"This is from default implementation, age is {age}"); } } public class AgedObject : IAgeable { public void SetAge(int age) { Console.WriteLine($"Set age, age is {age}"); } // new C#8 default implementation in interface } public class AgedObjectExplicit : IAgeable { public void SetAge(int age) { Console.WriteLine($"Set age, age is {age}"); } // still override with class public void SetAgeDefaultImplementation(int age) { Console.WriteLine($"This is class implementation, age is {age}"); } } } |
And extract of consuming code:
1 2 3 4 5 6 7 8 |
var o = new AgedObject(); var o1 = new AgedObjectExplicit(); o.SetAge(12); ((IAgeable)o).SetAgeDefaultImplementation(12); o1.SetAge(22); ((IAgeable)o1).SetAgeDefaultImplementation(22); |
Output:
My comment: To be honest, I don’t like this default interface implementation feature. Why? Because in my book, interfaces are lightweight, blueprints, contracts, abstractions and should not contain any implementation. Furthermore, I think abstract classes should be used for that purposes.
But, I agree, this default interface implementations can get handy in some cases. Default interface implementations enable developers to upgrade an interface while still enabling any implementors to override that implementation. Users of the library can accept the default implementation as a non-breaking change. If their business rules are different, they can override.
Use it with care, I will try use this new feature as less as possible – well, at least on start.
For more information about this feature, check out this link: https://docs.microsoft.com/en-us/dotnet/csharp/tutorials/default-interface-members-versions
More patterns in more places
Switch expressions
Very often, switch statement produce/return value for each case. In this case, C#8 handles this situation with new switch expression mechanism. Let’s take a look.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 |
namespace Jenx.Cs8NewFeatures.PatternMatchingEnhancements { public enum WeekPeriod { StartWeek, MidWeek, EndWeek, Weekend } public class PatternExpressionHelper { public static string GetDaysByWeekPeriodWithSwitchStatements(WeekPeriod weekPeriod) { return weekPeriod switch { WeekPeriod.StartWeek => "Monday & Tuesday", WeekPeriod.MidWeek => "Wednesday", WeekPeriod.EndWeek => "Thursday & Friday", WeekPeriod.Weekend => "Saturday & Sunday", _ => "undefined" }; } } } |
Calling code:
1 2 3 4 5 |
// switch expressions var endWeekDays = PatternExpressionHelper.GetDaysByWeekPeriodWithSwitchStatements(WeekPeriod.EndWeek); var weekendDays = PatternExpressionHelper.GetDaysByWeekPeriodWithSwitchStatements(WeekPeriod.Weekend); Console.WriteLine(endWeekDays); Console.WriteLine(weekendDays); |
Output:
My comment: In my opinion, this is nice new feature introduced in C#8. Code is cleaner and lighter (in relation to classical switch statement with return value cases).
Property patterns
The property pattern enables you to match on properties of the object examined. Let’s take a look at some exmaples:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 |
namespace Jenx.Cs8NewFeatures.PatternMatchingEnhancements { public class Tree { public Tree(TreeType treeType) { TreeType = treeType; } public TreeType TreeType { get; private set; } } public enum TreeType { Undefined, Oak, Spruce, Pine, Maple, Fir, Hornbeam, Limetree } public enum TreeDivision { Unknown, Deciduous, Coniferous } public class PatternExpressionHelper { public static TreeDivision GetTreeDivision(Tree tree) => tree switch { { TreeType: TreeType.Fir } => TreeDivision.Coniferous, { TreeType: TreeType.Hornbeam } => TreeDivision.Deciduous, { TreeType: TreeType.Limetree } => TreeDivision.Deciduous, { TreeType: TreeType.Maple } => TreeDivision.Deciduous, { TreeType: TreeType.Oak } => TreeDivision.Deciduous, { TreeType: TreeType.Spruce } => TreeDivision.Coniferous, { TreeType: TreeType.Pine } => TreeDivision.Coniferous, _ => TreeDivision.Unknown }; public static bool IsMale(Person person) => person switch { { Gender: Gender.Female } => false, _ => true }; } } |
And if I consume code, like:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 |
//case #1: Property expressions var tree1 = new Tree(TreeType.Oak); var tree2 = new Tree(TreeType.Pine); var tree3 = new Tree(TreeType.Spruce); var tree1Is = PatternExpressionHelper.GetTreeDivision(tree1); var tree2Is = PatternExpressionHelper.GetTreeDivision(tree2); var tree3Is = PatternExpressionHelper.GetTreeDivision(tree3); Console.WriteLine($"Tree #1, {tree1.TreeType.ToString()} is a {tree1Is}"); Console.WriteLine($"Tree #2, {tree2.TreeType.ToString()} is a {tree2Is}"); Console.WriteLine($"Tree #3, {tree3.TreeType.ToString()} is a {tree3Is}"); Console.ReadLine(); //case #2: Property patterns var person1 = new Person {Name="John"}; var person2 = new Person {Name="Monica", Gender = Gender.Female }; Console.WriteLine($"Person: {person1.Name} is {(PatternExpressionHelper.IsMale(person1) ? "a men": "a woman")}"); Console.WriteLine($"Person: {person2.Name} is {(PatternExpressionHelper.IsMale(person2) ? "a men" : "a woman")}"); Console.ReadLine(); |
I get this:
My comment: What to say: awesome new feature!
using declaration
Using declaration simplifies using statements, by letting compiler when to release important resource. Let’s look at example:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 |
namespace Jenx.Cs8NewFeatures.UsingDeclaration { public static class UsingDeclarationHelper { public static void RollUsingDeclarationExample() { // old way, by using statement, explicit definition where to release // resource using (var myResource1 = new MyResource()) { Console.WriteLine("start using of important resource: myResource1"); using (var myResource2 = new MyResource()) { Console.WriteLine("start using of important resource: myResource2"); } Console.WriteLine("release of myResource2"); } Console.WriteLine("release of myResource1"); // new C#8 feature: using decalration. Compiler release resource automatically // when no longer in use... using var myResource3 = new MyResource(); Console.WriteLine("start using of important resource: myResource3"); using var myResource4 = new MyResource(); Console.WriteLine("start using of important resource: myResource4"); Console.WriteLine("release of myResource3"); var t = myResource4; //still in usage Console.WriteLine("release of myResource4"); } } public class MyResource : IDisposable { public void Dispose() { Console.WriteLine("My resource released!"); } } } |
The output is:
My comment: In general good idea, but for now I will stay with explicit using statement. In my opinion when the code is more complex, this using statement can behaves as auto-magic by releasing resources auto-magically. But, as I said, generally good feature, because compiler knows at compile time when resource can be released/marked for disposal.
Static local functions
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 |
public static int SumWithLocalStaticFunctions(int a, int b) { // call local function (new in Cs7) DiplayHeader(); int sum = 0; // call local function DiplayResult(); sum = a + b; DiplayResult(); #region Local Function declarations // local functions void DiplayResult() => Console.WriteLine($" local="" function:="" suma="" is:="" {sum}");="" static="" void="" diplayheader()=""> Console.WriteLine($"Static local function: Entering experiment: Local functions in {nameof(SumWithLocalStaticFunctions)}"); // local function void DiplaySummaryNoStatic() { Console.WriteLine(""); Console.WriteLine("Local Function...."); Console.WriteLine("*** Sumary ****"); Console.WriteLine($"Total of:"); Console.WriteLine($"A: {a}, and"); Console.WriteLine($"B: {b} is"); Console.WriteLine($"{sum}"); }; // NEW: local static function (self contained) static void DiplaySummaryStatic(int sum1, int sum2, int sum) { Console.WriteLine(""); Console.WriteLine("Static Local Function...."); Console.WriteLine("*** Sumary ****"); Console.WriteLine($"Total of:"); Console.WriteLine($"A: {sum1}, and"); Console.WriteLine($"B: {sum2} is"); Console.WriteLine($"{sum}"); }; #endregion Local Function declarations // after declaration calls DiplaySummaryNoStatic(); DiplaySummaryStatic(a, b, sum); return sum; } |
Output:
My comment: Nice new feature, but should be used carefully. In my opinion, this feature is useful because developer has ability to structure code better inside functions without usage of (type-wide) private functions. On the other hand, overuse can lead to large functions and hard to read code. So, developers should use this feature with care!
Asynchronous streams
1 2 3 4 5 6 7 8 |
public static async IAsyncEnumerable<int> Count() { for (int i = 0; i < 20; i++) { await Task.Delay(1000); // do something async... yield return i; } } |
Calling for-each loop must contain async , e.g.
1 2 3 4 |
await foreach (var counter in AsynchronousStreams.AsyncEnumerableHelper.Count()) { Console.WriteLine($"Async Enumerable No. {counter}"); } |
My comment: Similar as with ordinary yield construct, now async iterator return result to the calling code. Nice and very useful.
Indices and ranges
Two new types and two new operators related to collection handling are introduced:
- types:
System.Index
,System.Range
- operators:
^
and..
Let’s take a look at this method:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 |
public static void YearReport() { var months = new string[] { // indexes can be a bit tricky // index ^ "January", // 0 12 "February", // 1 11 "March", // 2 10 "April", // 3 9 "May", // 4 8 "June", // 5 7 "July", // 6 6 "August", // 7 5 "September", // 8 4 "October", // 9 3 "November", // 10 2 "December", // 11 1 // Lenght = 12 0 }; // new // ^ index operator // .. range operator Console.WriteLine($"All months:\n{string.Join(",\n", months[..]) }"); Console.WriteLine($"3rd month in year is: {months[2]}"); Console.WriteLine($"last month in year is: {months[^1]}"); Console.WriteLine($"last - 1 month in year is: {months[^2]}"); Console.WriteLine($"Summer months are: {string.Join(", ", months[5..8]) }"); Console.WriteLine($"First half of the year: {string.Join(", ", months[..6]) }"); Console.WriteLine($"Second half of the year: {string.Join(", ", months[6..]) }"); Console.WriteLine($"First 2/3 of the year: {string.Join(", ", months[..^4]) }"); Console.WriteLine($"Last 1/3 of the year: {string.Join(", ", months[^4..]) }"); // etc.... } |
Output is:
My comment: Very clean and easy way of handling indexes and ranges in arrays. Very good extension to C# language!
The only awkward think here is that first item in array with index 0, but from end, operator ^1 returns last item in this collection. Something to keep in mind!
Null-coalescing assignment
New operator named null-coalescing assignment (??=) introduced in C# 8.
Definition is simple: You can use the ??=
operator to assign the value of its right-hand operand to its left-hand operand only if the left-hand operand evaluates to null
.
Let’s make an example:
1 2 3 4 5 6 7 8 9 10 11 12 13 |
public static class NullCoalescingHelper { public static void PlayWithNullCoalescingOperator() { var a = "original value"; a ??= "new value"; Console.WriteLine($"Assignement to not null object: {a}"); a = null; a ??= "try new value again"; Console.WriteLine($"Assignement to null object: {a}"); } } |
And the output of this method is:
My comment: I like this new feature, it can be very useful and will simplify code a lot in some cases!
Enhancement of interpolated verbatim strings
Small enhancement with string interpolation and verbatims. With pre-C#8 (Preview 5) we had this:
In C#8 (Preview 5), compiler is fine with this code:
My comment: Small, but welcome enhancement to the language.
Conclusion
Until then, happy coding!