Here’s an overview of the features offered by C# 7.0, C# 7.1, and C# 7.2 – comparing them to previous versions of C#: digit separators, binary literals, expression-bodied members, out var, tuples, pattern matching, and more!
References to the chapter numbers for more information references Professional C# 7 and .NET Core 2.0.
Digit Separators
The digit separators make the code more readable. You can add _ to separate numbers when declaring variables. The compiler just removes the _. The following code snippet looks a lot more readable with C# 7. C# 7.2 allows to put the separator at the beginning:
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// C# 6 | |
long n1 = 0x1234567890ABCDEF; | |
// C# 7 | |
long n2 = 0x1234_5678_90AB_CDEF; | |
// C# 7.2 | |
long n2 = 0x_1234_5678_90AB_CDEF; |
More information on digit separators in chapter 2, Core C#!
Binary Literals
C# 7 offers a new literal for binaries. Binaries can only have values 0 and 1. Now the digit separator becomes really important:
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
uint binary1 = 0b1111_0000_1010_0101_1111_0000_1010_0101; |
More information on binary literals in chapter 2, Core C#!
Expression-Bodied Members
C# 6 allows expression-bodied methods and properties. With C# 7, expression bodies can be used with constructors, destructors, local functions, property accessors and more. Here you can see the difference with property accessors between C# 6 and C# 7:
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// C# 6 | |
private string _firstName; | |
public string FirstName | |
{ | |
get { return _firstName; } | |
set { Set(ref _firstName, value); } | |
} | |
// C# 7 | |
private string _firstName; | |
public string FirstName | |
{ | |
get => _firstName; | |
set => Set(ref _firstName, value); | |
} |
More information on expression-bodied members in chapter 3, Objects and Types!
Out Var
Previous to C# 7, out variables had to be declared before its use. With C# 7, the code is reduced by one line, as the variable can be declared on use:
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// C# 6 | |
string n = "42"; | |
int result; | |
if (string.TryParse(n, out result) | |
{ | |
Console.WriteLine($"Converting to a number was successful: {result}"); | |
} | |
// C# 7 | |
string n = "42"; | |
if (string.TryParse(n, out var result) | |
{ | |
Console.WriteLine($"Converting to a number was successful: {result}"); | |
} |
More information on out var in chapter 3, Objects and Types!
Non-trailing Named Arguments
C# supports named arguments that are required with optional arguments, but can support readability in any cases. With C# 7.2, non-trailing named arguments are supported. Argument names can be added to any argument with C# 7.2:
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// C# 7.0 | |
if (Enum.TryParse(weekdayRecommendation.Entity, ignoreCase: true, | |
result: out DayOfWeek weekday)) | |
{ | |
reservation.Weekday = weekday; | |
} | |
// C# 7.2 | |
if (Enum.TryParse(weekdayRecommendation.Entity, ignoreCase: true, | |
out DayOfWeek weekday)) | |
{ | |
reservation.Weekday = weekday; | |
} |
More information on named arguments in chapter 3, Objects and Types!
Readonly Struct
Structures should be read-only (with some exceptions). Using C# 7.2 it’s possible to declare the struct with the readonly modifier, so the compiler verifies that the struct is not changed. This guarantee can also be used by the compiler to not copy a struct passing it as a parameter, but instead passing a reference:
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
public readonly struct Dimensions | |
{ | |
public double Length { get; } | |
public double Width { get; } | |
public Dimensions(double length, double width) | |
{ | |
Length = length; | |
Width = width; | |
} | |
public double Diagonal => Math.Sqrt(Length * Length + Width * Width); | |
} |
More information on readonly struct in chapter 3, Objects and Types!
In Parameters
C# 7.2 also allows the in modifier with parameters. This guarantees that a passed value type is not changed, and it can be passed by reference to avoid a copy:
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
static void CantChange(in AStruct s) | |
{ | |
// s can’t change | |
} |
ref, in, and out modifiers are covered in chapter 3, Objects and Types!
Private Protected
C# 7.2 adds a new access modifier: private protected
. The access modifier protected internal
allows access to the member if it’s used from a type in the same assembly, or from a type from another assembly that derives from the class. With private protected
it’s an AND instead of an OR – access is only allowed if the class derives from the base class and is in the same assembly.
Access modifiers are covered in chapter 4, Object Oriented Programming with C#!
Target-Typed Default
With C# 7.1, a default literal is defined which allows a shorter syntax compared to the default operator. The default operator always requires the repetition of the type which is now not needed anymore. This is practical with complex types:
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// C# 7.0 | |
int x = default(int); | |
ImmutableArray<int> arr = default(ImmutableArray<int>); | |
// C# 7.1 | |
int x = default; | |
ImmutableArray<int> arr = default; |
The default literal is covered in chapter 5, Generics!
Local Functions
Previously to C# 7 it was not possible to declare a function within a method. You could create a lambda expression, and invoke this one as shown here in the C# 6 code snippet. With C# 7, a local function can be declared within a method – the local function is only accessible within the scope of the method:
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// C# 6 | |
public void SomeFunStuff() | |
{ | |
Func<int, int, int> add = (x, y) => x + y; | |
int result = add(38, 4); | |
Console.WriteLine(result); | |
} | |
// C# 7 | |
public void SomeFunStuff() | |
{ | |
int add(int x, int y) => x + y; | |
int result = add(38, 4); | |
Console.WriteLine(result); | |
} |
Local functions are explained in chapter 13, Functional Programming!
Tuples
Tuples allow combining objects of different types. Previous to C# 7, tuples have been part of the .NET Framework with the Tuple
class. The members of the tuple can be accessed with Item1
, Item2
, Item3
… Using C# 7, tuples are part of the language, and you can define the names of the members:
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// C# 6 | |
var t1 = Tuple.Create(42, "astring"); | |
int i1 = t1.Item1; | |
string s1 = t1.Item2; | |
// C# 7 | |
var t1 = (n: 42, s: "magic"); | |
int i1 = t1.n; | |
string s1 = t1.s; |
Other than that, the new tuples are value types (ValueTuple) whereas the Tuple type is a reference type. All the changes with tuples are covered in chapter 13, “Functional Programming”.
Inferred Tuple Names
C# 7.1 extends tuples by automatically inferring tuple names, similar to anonymous types. With C# 7.0, the members of the tuple always need to be named. In case the tuple member should have the same name as the property or field you assign to it, with C# 7.1, if the name is not supplied, it has the same name as the assigned member:
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// C# 7.0 | |
var t1 = (FirstName: racer.FirstName, Wins: racer.Wins); | |
int wins = t1.Wins; | |
// C# 7.1 | |
var t1 = (racer.FirstName, racer.Wins); | |
int wins = t1.Wins; |
Inferred tuple names are covered in chapter 13, Functional Programming.
Deconstructors
No, this is not a typo. Deconstructors are not destructors. A tuple can be deconstructed to separate variables, such as shown in the following code snippet. It’s also possible to deconstruct a Person object – if a Deconstruct method is defined:
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
(int n, string s) = (42, "magic"); | |
var p1 = new Person("Tom", "Turbo"); | |
(string firstName, string lastName) = p1; |
Deconstruction is covered in chapter 13, Functional Programming.
Pattern Matching
With pattern matching, the is operator and the switch statement have been enhanced with three kinds of patterns: the const pattern, the type pattern, and the var pattern. The following code snippet shows patters with the is operator. The first check for a match matches the constant 42, the second match a Person object, and the third match every object with the var pattern. Using the type and the var pattern, a variable can be declared for strongly typed access. Using the switch statement, you can use the same patterns with the case clause. You can also declare a variable to be strongly typed in case the pattern matches. You can also use when to filter the pattern on a condition:
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
public void PatternMatchingWithIsOperator(object o) | |
{ | |
if (o is 42) | |
{ | |
} | |
if (o is Person p) | |
{ | |
} | |
if (o is var v1) | |
{ | |
} | |
} | |
public void PatternMatchingWithSwitchStatement(object o) | |
{ | |
swtich (o) | |
{ | |
case 42: | |
break; | |
case Person p when p.FirstName == "Katharina": | |
break; | |
case Person p: | |
break; | |
case var v: | |
break; | |
} | |
} |
Pattern matching is covered in chapter 13, Functional Programming.
Throw Expressions
Throwing exceptions was only possible with a statement, but not in an expression. Thus, receiving a parameter with a constructor, extra checks had for null were necessary to throw an ArgumentNullException. With C# 7, exceptions can be thrown in expressions, thus it is possible to throw the ArgumentNullException when the left side is null – using the coalescing operator.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// C# 6 | |
private readonly IBooksService _booksService; | |
public BookController(BooksService booksService) | |
{ | |
if (booksService == null) | |
{ | |
throw new ArgumentNullException(nameof(b)); | |
} | |
_booksService = booksService; | |
} | |
// C# 7 | |
private readonly IBooksService _booksService; | |
public BookController(BooksService booksService) | |
{ | |
_booksService = booksService ?? throw new ArgumentNullException(nameof(b)); | |
} |
Throw expressions are covered in chapter 14, Errors and Exceptions.
Async Main
Before C# 7.1, the Main method always needs to be declared of type void. With C# 7.1, the Main method can also be of type Task and use the async and await keywords:
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// C# 7.0 | |
static void Main() | |
{ | |
SomeMethodAsync().Wait(); | |
} | |
// C# 7.1 | |
async static Task Main() | |
{ | |
await SomeMethodAsync(); | |
} |
Asynchronous programming is covered in chapter 15, Asynchronous Programming.
Reference Semantics
.NET Core has a big focus on enhancing the performance. With additions to the reference semantics. Previous to C# 7, the ref keyword could be used with parameters, to pass value types by reference. Now it’s also possible to use the ref
keyword with the return type and with local variables.
The following code snippet shows some of the features on ref semantics.
- The method
GetNumber
to return a reference to anint
. This way, the caller has direct access to the element in the array, and can change its content. - With C# 7.2, the
readonly
modifier can be added to ref returns. This way the caller can’t change the content of the returned value, but still reference semantics is used, a copy of the value type when returning the result can be avoided. The caller receives a reference but isn’t allowed to change it. - Previous to C# 7.2, C# allows to create reference types (a class), and value types (a struct). However, the struct can also be stored on the heap when boxing takes place. With C# 7.2, a type can be declared that is only allowed on the stack: ref struct:
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// ref returns and ref locals with C# 7.0 | |
int[] _numbers = { 3, 7, 11, 15, 21 }; | |
public ref int GetNumber(int index) | |
{ | |
return ref _numbers[index]; | |
} | |
// ref readonly with C# 7.2 | |
int[] _numbers = { 3, 7, 11, 15, 21 }; | |
public ref readonly int GetNumber(int index) | |
{ | |
return ref _numbers[index]; | |
} | |
// ref struct with C# 7.2 | |
ref struct OnlyOnTheStack | |
{ | |
} |
The new features for references are covered in Chapter 17, Managed and Unmanaged Memory.
Summary
C# 7.x offers many, many, many new practical features. I hope you enjoyed the short summary. All these (among many other topics on .NET Core, ASP.NET Core, and Windows apps) are covered in detail my new book Professional C# 7 and .NET Core 2.0.
Enjoy creating code and my new book,
Christian
2 thoughts on “C# 7.x Features”