What's new in C# 7.0 through 7.3?

What's new in C# 7.0 through 7.3?

Introduction

In C# version 7.0 which is released within Visual Studio 2017, we are facing some newer and cooler features against version 6.0. Some features like using out keyword and tuple type for returning more than one value as a result. In version 7.1, we see some newer language version capabilities like asynchronous main method and some other improvements on language features which are continued to version 7.2 and 7.3. In version 7.3 there are also provided more features that enable more safe code writings ...

New features in C# 7.0:

Out variable: In the usual method declaration, we could only assign one output type to the method and expect just one parameter as the output. Using out keyword in the signature of a method makes it possible to return more than one outputs. In the earlier versions of the C# programming language, there would need to provide separate declaration and initialization for "out" variables, but in this version, we could define it in the method signature.

It can also be declared implicitly, be initialized at the compile-time, and being used from the method line in which it is defined to the end of the scope.

Tuples and deconstruction: Tuples have existed before C# 7.0 but there are some additional features for this item in version 7.x. Tuples are value types that store a set of values with different types. For example, you can store int and float in the same set of variables. But before C# 7.0, you couldn't assign and access the elements of this data structure using their semantic name. Tuples can be considered as an alternative for returning multiple values from a method.

Pattern Matching: This capability of the C# programming language was introduced in version 7.0. Using this feature helps us to manage the control flow by making decisions based on any variable's type, value and the value of its properties. The main expressions and statements which support the pattern matching are "is" expression, Switch statement, and Switch expression (which is introduced in C# 8.0 and I will talk about it in my further articles!). there are multiple patterns matching items, those proposed in C# version 7.X are:

is expressions are suitable for type checking at run-time execution. It has three forms of pattern matching, listed below:

var v = expr as Type;
if (v != null) { // code using v }


// writing the if statement with "is expression"


if (expr is Type v) { // code using v }

  • Declaration patterns: this occurs at the run-time execution and it checks the feasibility of type conversion between the given expression and the existing one.
  • Var pattern: it is an implicit assignment between an expression and an anonymous var type variable.
  • Constant pattern: checks the equality between the given statement and a constant value.

Switch statement: This is the same as the switch statements in the previous versions, with some additional changes:

  • we can check almost any type in the case statements, truly more than integral types, Enum types, string or nullable types.
  • In this version, just the first match case is executed and the rest are skipped. so the order of cases is important.
  • we can use the "is" expression in the case matching for implicit assignment.
  • we can use the "when" clause for applying some conditional rules to the case statements.

Local functions: These are some private methods that could be defined inside another method and be accessible just in that scope. They are some methods that are more suitable to define the public iterator, and async iterator method, constructors, property accessors, event accessors, anonymous methods, lambda expressions, finalizers and so many other local functions!

At first glance, there may be some resemblance between the local functions and lambda expressions but there are some important differences:

  • Naming: the local functions are declared as normal methods but the lambda expressions are some anonymous methods.
  • Function signature and lambda expression types: the input and output types of a lambda expression are based on two built-in delegates Action and Func. Both delegates accept some inputs, but the Action has a void return type and the Func as a generic return type which could be specified at the compilation. But the local functions are usually defined as the normal methods so the input and output types are declared in their signatures.
  • Definite assignment: lambda expressions are declared at the runtime but the local methods are declared at the compile time. So in the lambda expressions, the initialization and assignments are defined before the lambda expression invocation. But in the local method, the definition of the local method could become after or before using it anywhere in the scope. These differences say that It is better to use local functions in the iterator methods.
  • Yield keyword: In the lambda expressions we cannot use the yield return but in the local methods we can.

Expanded expression-bodied members: It is not a new feature in C# version 7.0 to write the method body in form of expressions. But in version 7.0 we are able to write the constructors, finalizers, get and set methods, of the property accessors and indexers in form of expressions. You can see some samples in the code below:

No alt text provided for this image

Ref locals and returns: " ref " keyword provides direct access to a specific object in the memory. It helps to prevent multiple invocations and incorrect assignments in the programs. In the following piece of code, we have defined an array in the NumberStore and want to find the first number in this array that is equal or greater than the target value. If we find it, we have to return a reference to a specific element of that array and replace it with a new value, and if there is no such number, we have to return a reference to the first element of that array.

We usually can do it in two ways. The simplest way is to traverse the array, find the index of that element, check the criteria and replace it with a specific number. So, there might occur some inevitable mistakes: misplacement of the same index in another array or replacing the wrong index in the specified array!

The most efficient way is to use the ref keyword to get direct access to that part of the array and apply the changes directly to it.

No alt text provided for this image

In this part, you can see the difference between a normal variable (store) and a ref variable (value).

No alt text provided for this image


In the following paragraphs, I have listed the new changes of C# version 7.1 with a brief description...

?async Main method: Asynchronous feature is one of the key features of C# version 5.0. But in version 5.0 we were not allowed to design an asynchronous entry point (the Main method) in our program. in that version we must use the DoAsyncWork() method to apply asynchrony and invoke the GetAwaiter() on the return result of the Main method:

static int Main()
{
    return DoAsyncWork().GetAwaiter().GetResult();
}

But in version 7.1 we can use the Async and Await mutual keywords to execute an asynchronous Main method:

static async Task<int> Main()
{
    // This could also be replaced with the body
    // DoAsyncWork, including its await expressions:
    
    return await DoAsyncWork();
}

static async Task Main()
{
    await SomeAsyncMethod();
}


The async methods can return three types of outputs: Task for the methods without any return values (similar to the void synchronous methods), Task<TResult> for methods with return values (similar to non-void synchronous methods) and void for event handlers.

Default literal expression: In the C# version 7.1 there is a new keyword named "default" which could be a more efficient way for initializing a variable, declaring the default value for an optional parameter in the signature of methods, returning statement, and default value in the method invocation procedure.

Inferred tuple element names: In this version 7.1, we can assign semantic names to the elements of tuple value type and access them using these names. There are multiple ways to assign names to the tuple items:

// 1st form of definition:
(int Count, string Label) namedTuple = (5, "Colors used in the map");
Console.WriteLine($"{namedTuple.Count}, {namedTuple.Lable}");

// 2nd form of definition:
var tupleStart = (Count: 5, Label: "Colors used in the map");
Console.WriteLine($"{tupleStart.Count}, {tupleStart.Label}");

// 3rd form of definition:
int Count = 5;
string Label = "Colors used in the map";
var pair = (Count, Label); // element names are "Count" and "Label"




// deconstructing or unpackaging the items of a tuple value type 

(int max, int min) = Range(numbers);
Console.WriteLine(max);
Console.WriteLine(min);

Pattern matching on generic types parameter: In C# 7.1, we can extend the equality checking of type, value or references to structs and classes. It avoids us to use the "as" operator which is used to the explicit conversion of the value to a reference type or nullable type, boxing or unboxing for this purpose.

New changes in C# version 7.2 ...

Writing safe efficient code: safe code prevents some kind of errors named as memory access errors; like buffer overruns and stray pointer. In C# 7.2 we can use some of its capabilities to write a safer code; such as access fixed fields without, reassign ref local variables, use initializer on stackalloc arrays, use in modifier to pass a parameter as a read-only reference, return a read-only reference type, create read-only structs, use ref struct to have direct access to the structs memory, etc.

Non-trailing named arguments: In the previous versions when there was a need to invoke and initialize a method, we were able to invoke the method in different ways. In the code snippet below, you see the definition of a method and the valid invocation form of it:

//The method definition.

static void PrintOrderDetails(string sellerName, int orderNum, string productName)
    {
        // some implementation ...
    }




// The method can be called in the normal way, by using positional arguments.
PrintOrderDetails("Gift Shop", 31, "Red Mug");


// Named arguments can be supplied for the parameters in any order.
PrintOrderDetails(orderNum: 31, productName: "Red Mug", sellerName: "Gift Shop");
PrintOrderDetails(productName: "Red Mug", sellerName: "Gift Shop", orderNum: 31);


// Named arguments mixed with positional arguments are valid
// as long as they are used in their correct position.

PrintOrderDetails("Gift Shop", 31, productName: "Red Mug");

PrintOrderDetails(sellerName: "Gift Shop", 31, productName: "Red Mug"); // C# 7.2 
PrintOrderDetails("Gift Shop", orderNum: 31, "Red Mug"); // C# 7.2 

Leading underscore in numeric literals: For avoiding misreading/writing of the large constant numbers, this language has provided some special signs like "_" (underscore) for separating a specific number of digits, for example, 3 digits in base 10 numbers of 4 in Hex numbers, and so on, and "0b" at the beginning of the numbers indicates a binary format.

//Binary format of numbers:

public const int Sixteen =   0b0001_0000;
public const int ThirtyTwo = 0b0010_0000;
public const int SixtyFour = 0b0100_0000;
public const int OneHundredTwentyEight ?= 0b1000_0000;

//Digit separators can be use for long, float, double, and decimals

public const long BillionsAndBillions = 100_000_000_000;

public const double AvogadroConstant = 6.022_140_857_747_474e23;
public const decimal GoldenRatio = 

1.618_033_988_749_894_848_204_586_834_365_638_117_720_309_179M;

Private protected access modifier: this access modifier is defined in C# version 7.2. Members of this type of class have access to the members of the current class and its derived class in the same assembly file.

Conditional ref expressions: "ref" operators were introduced in version 7.0. In this version and later, we can apply some conditional rules on the "ref" local variables:

ref var r = ref (arr != null ? ref arr[0] : ref otherArr[0]);

In the above code if the condition is true the "ref arr[0]" will be returned and if it is false the "ref otherArr[0]" will.

And finally, the improvements of C# version 7.3 are categorized into two main groups:

  1. some improvements on the previous features like using the "==" and "!=" for evaluating the tuple values, using expression variables in more situations, using auto-implemented properties for more types, expanding on method resolutions using the in operator as their arguments, making clear definition for overloading mechanism.
  2. enabling code safely with reassigning the ref local variables, using initializers for stackalloc structure, and using additional generic constraints.

要查看或添加评论,请登录

Elahe Dorani的更多文章

  • What's new in C# 8.0?

    What's new in C# 8.0?

    Introduction C# as a type-safe, object-oriented and component-oriented programming language is at the heart of the .Net…

  • What is new in C# 6.0?

    What is new in C# 6.0?

    In the 6.0 version of the C# programming language, the main focus of language designers were on adding some features to…

    1 条评论
  • Chapter 18: Asynchronous Language Features

    Chapter 18: Asynchronous Language Features

    Introduction The CPU of digital gadgets are the brain of devices. The operating systems on them would consider an…

    1 条评论
  • Chapter 10: LINQ

    Chapter 10: LINQ

    Introduction LINQ, the Language Integrated Query, is an integrated set of technologies using to apply some queries on…

    3 条评论
  • Chapter 9: Delegates, Lambdas, and Events

    Chapter 9: Delegates, Lambdas, and Events

    Introduction Delegates are some types that provide a reference to the methods. Events are some notification occurred in…

  • Chapter 8: Exceptions

    Chapter 8: Exceptions

    Introduction In the execution process of any programs, there could occur some errors and it is on the CLR decide to run…

  • Garbage Collection

    Garbage Collection

    The garbage collector is an automatic mechanism to detect and collect the dead object’s memory space and make it…

  • Chapter 7: Object Lifetime

    Chapter 7: Object Lifetime

    Introduction C# language programming supports the object-oriented paradigm. It means that almost all operations in this…

  • Chapter 6: Inheritance

    Chapter 6: Inheritance

    C# as an object-oriented language has some essential features which make it possible to produce object-oriented…

  • chapter 5: Collections

    chapter 5: Collections

    Introduction The data is the main concept we want to deal with in every program we write. The most important thing we…

社区洞察

其他会员也浏览了