Key Takeaways

Ranges easily define a sequence of data, replacing the Enumberable.Range().

Recursive patterns bring an F#-like construct to C#.

Recursive patterns are an awesome feature, giving you the flexibility to test data against a sequence of conditions and perform further computations based on the conditions met.

Ranges are useful for generating sequences of numbers in the form of a collection or a list.

Jan 21, 2015 is one of the most important days in the C# modern history. On this day, the C# Gurus like Anders Hejlsberg and Mads Torgersen and others have discussed the future of C# and considered in which directions the language should be expanded.

The first result of this meeting was C# 7. The seventh version added some new features and brought a focus on data consumption, code simplification and performance. The new proposals for C# 8 do not change this focus on features, but this might yet change in the final release.

Figure -1- C# 7..8 features focus

In this article, I will discuss two features proposed for C# 8. The first one is the Ranges and the second one is Recursive Patterns, both of which belong to the category of Code Simplification. I will explain them in detail with many examples, and I will show you how those features can help you to write better code.

Ranges easily define a sequence of data. It is a replacement for Enumerable.Range() except it defines the startand stop points rather than start and count and it helps you to write more readable code.

Example

foreach(var item in 1..100)
{
Console.WriteLine(item);
}

Recursive Patterns matching is a very powerful feature, which allows code to be written more elegantly, mainly when used together with recursion. The RecursivePatterns consists of multiple sub-patterns like as Positional Patterns, e.g.,var isBassam = user is Employee ("Bassam",_), Property Patterns, e.g.,p is Employee {Name is "Mais"}, Var Pattern, Discard Pattern (‘_’),and so forth.

Example

Recursive Patterns with tuples (The below example is also known as Tuple Pattern)

The case (_, 43) can be interpreted as follows. The first part, ‘_‘,means ignore the item’s property Name but the second part, the Age, must be 43. If the employee tuple contains this pair of information (any string, 43),then the case block will be executed. As shown in figure -1-.

Ranges

This feature is about delivering two new operators (Index operator ‘^’ and range operator ‘..’) that allow constructing System.Index and System.Range objects and using them to index or slice collections at runtime. The new operators are syntactic sugar and making your code more clean and readable. The code for the operator index ^ is implemented in System.Index and for the range ‘..’ in System.Range.

System.Index

Example

System.Range

Ranges way to access "ranges" or "slices" of collections. This will help you to avoid LINQ and making your code compact and more readable. You can compare this feature with Ranges in F#.

New style

Old style

var thirdItem = array [2];

// Code behind: array [2]

var thirdItem = array [2];

var lastItem = array [^1];

// Code Behind: [^1] = new Index(1, true); true = bool fromEnd

var lastItem = array [array.Count -1];

var lastItem = array.Last; // LINQ

var subCollection = array[2..^5]; // Output: 2, 3, 4, 5

// Code Behind: Range.Create(2, new Index(5, true)); as you see here we have used the both operators! Range and Index. The Range is for the operator … and the index is for the operator ^. Means skip until the index 2 from the begin and ^5 means ignore the first 5 elements from behind.

var subCollection = array.ToList().GetRange(2, 4);

or with LINQ

var subCollection = array.Skip(2).Take(4);

Examples

Consider the following array:

var array = new int[] {0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10};

Value

0

1

2

3

4

5

6

7

8

9

10

We can access the array values with the following indexes:

Index

0

1

2

3

4

5

6

7

8

9

10

Now we cut a slice view from this array as below:

var slice= array[2..5];

Value

2

3

4

We can access the slice values with the following indexes:

Index

0

1

2

Note: the start index is inclusive (included to the slice), and the end index is exclusive (excluded from the slice).

var slice1 = array [4..^2]; // Range.Create(4, new Index(2, true))

and the slice1 will be of type Span<int>. [4..^2] Skip from the begin until the index 4 and skip 2 from the ending.

Bounded Ranges

In the bounded ranges, the lower bound (start index) and the upper bound(end index) are known or predefined.

array[start..end] // Get items from start-1 until end-1
array[start..end:step] // Get items from start-1 until end-1 by step

The above Range syntax (step at end) is inspiredfrom Python. Python supports the following syntax(lower:upper:step), with :stepbeing optional and :1 by default, but there are some wishes in the community to use the F# syntax (lower..step..upper).

Range Pattern is one of the new proposed pattern matching which can be used to produce simple range checks. Range Pattern will allow you to use the range operator ‘..’ in the select case" statement (switch).

It is worth to mention that not everyone happy by using the “in” operator in Ranges. The community is divided between using “in” or “is”; you can follow up the whole discussions here: Open issues for C#range

Unbounded Ranges

When the lower bound is omitted, it's interpreted to be zeroor the upper bound is omitted, it's interpreted to be the length of the receiving collection.

Examples

array[start..] // Get items start-1 with the rest of the array
array[..end] // Get items from the beginning until end-1
array[..] // A Get the whole array

var world = helloWorldStr[^6..]; // Take the last 6 characters
Console.WriteLine(world); // Output: World

Ranges ForEach loops

Examples

Ranges implement IEnumerable<int> which allowing the iteration over a sequence of data.

foreach (var i in 0..10)
{
Console.WriteLine(“number {i}”);
}

Recursive Patterns

Pattern matching is one of the powerful constructs, which is available in many functional programming languages like F#. Furthermore, pattern matching provides the ability to deconstruct matched objects, giving you access to parts of their data structures. C# offers a rich set of patterns that can be used for matching.

Pattern matching was initially planned for C# 7, but after while .Net team has find that he need more time to finish this feature. For this reason, they have divided the task in two main parts. BasicPattern Matching, which is already delivered with C # 7, and the AdvancedPatternsMatching for C# 8. We have seen in C# 7 Const Pattern, Type Pattern, Var Pattern and the Discard Pattern. In the next C# 8 version, we will see more patterns like Recursive Pattern, which consist of multiple sub-patterns like the Positional Pattern, and Property Pattern.

To understand the Recursive Patterns, we need many code examples. I have defined two classes. Employee and Company as defined below which I use them to explain the Recursive Patterns.

Positional Pattern

Positional Pattern decomposes a matched type and performs further pattern matching on the values that are returned from it. The final value of this pattern/expression is true or false, which led to execute the code block or not.

In this example, I have used the pattern matching recursively. The first part is Positional Pattern employee is Employee(…)and the second part is the sub-patterns within the brackets (_,_, ("Stratec", _,_))

The code block after the ‘if’ statement will only executed if the conditions in the root Positional Pattern(employee object must be of type Employee) with its sub-pattern (_,_, ("Stratec",_,_) the company name must be “Stratec”) are satisfied, and the rest is discarded´, in other words, the if evaluates true only if the company name is Stratec.

Property Pattern

Property Patterns are straight forward. You can access a type fields and properties and apply a further pattern matching against them.

Compare the pattern matching code with the C# 6 and look how the C# 8 code is more explicit. The new style removes the redundant code and the type casting or the ugly operators like “typeof” or “as”.

Recursive Patterns

RecursivePatterns are nothing more than a combination of the above-described patterns. The type will be decomposed to subparts so that the subparts may be matched against subpatterns. Behind the scene, this pattern deconstructs the type by using the Deconstruct()method and applying onthe deconstructed value further pattern matching if needed. If your type does not have a Deconstruct() method or it is not a tuple, then you need to write it by yourself.

If you remove the Deconstruct method from the Company class above, you will havethe following error:

error CS8129: No suitable Deconstruct instance or extension method was found for type ‘Company’, with 0 out parameters and a void return type.

Let's get a look atthe Positional Pattern and Property Pattern.

Example

I have created two employees and two companies and map each employee to a company.

In the example above the case condition matching any employee with any data, it is a combination of the Deconstruction Pattern and the Discard Pattern. Now we will go one step further. We need only to filter the Stratec employees.

There is more than one approach to do that with pattern matching. We will replace/rewrite the below case condition from the example with some different techniques.

The above switch is working fine. If we move one of those switch case somewhere else up/down.Let’s say you will movecase Employee(_, _, _) employeeTmp:at the beginning (first case after the switch) as shown below:

error CS8120: The switch case has already been handled by a previous case.

error CS8120: The switch case has already been handled by a previous case.

error CS8120: The switch case has already been handled by a previous case

Figure -4- Error after changing the switch case position in SharpLab

The compiler knows that the cases after your most generic switch case cannot be reached (dead code) and telling you with the error that you are doing something wrong.

Pattern matching with Collections

Examples

switch (intCollection)
{
case [1, 2, var x ] =>
{
// This block code will executed and return if the first two items in intCollection is 1 and 2 and the third one will be copied into the variable x.
Console.WriteLine( $ "it's 1, 2, {x}", );
}
case [1,..20] =>
{
// This block will be executed if the intCollection starts with 1 and ends with 20.
);
case _ =>
{
// This block will be executed for any other use cases except the tow above defined.
}
}
if (intCollection is [.., 99, 100])
{
// Execute this block only if the lasts items in the collection are 99 and 100
}
if (intCollection is [1, 2, ..])
{
// Execute this block only if the first items in the collection are 1 and 2
}
if (intCollection is [1, .., 100])
{
// Execute this block only if the first items in the collection is 1 and the last one 100
}

Recursive Patterns (C# 8) Playground

Copy the below employee code example

Open in the web browser: https://sharplab.io

Paste the code and select “C# 8.0: RecusivePatterns (14 May 2018)”, then select the “Run” as shown in figure4 below.

Summary

Ranges is very useful to generate sequences of numbers in the form of a collection or a list. Combining Ranges with for each loops or and Pattern matching, etc., makes the C# syntax more simple and readable.

Recursive Patterns is the core of the Pattern matching. Pattern matching helps you to compare the runtime data with any data structure and decompose it into constituent parts or extract sub data from data in different ways and the compiler supporting you to check the logic the of your code.

Recursive Patterns is an awesome feature, it giving you the flexibility to testing the data against a sequence of conditions and performing further computations based on the condition met.

Good job on the write up, it's well presented, though there could be a little.more detail in the pattern composition.

That said, MS' implementation of pattern matching and discriminated unions is a objectively kind of lame. In a real FP language the construct would form the execution definition of the function, which is both shorter and easier to parse:

Normally, you will decompose the type to sub-types and in the temp you take only the required data. If the method need only the name and the age. You can define a temp value type tuple and take only the required information. It is really cool the method will get as input parameters only the required domain information and not the whole class.

I know, the example was very complex, but before that I have write very simple examples to show the concepts. With this example I just want to show the reader a very complex senario about PM, but you do not have to write like that otherwise :-) You know now that you can also write an ugly code with PM not only clean code :-)

I will quote some text from www.reddit.com about this article:

vlatheimpaler says:"Pattern matching I absolutely love in languages that do it well. I love it in F#, although I don't use F# very much. But I use Erlang and Elixir a lot and pattern matching is an essential feature of those languages.After using those and then going back to some complex code in another language, I miss it a lot. How many times have you seen or written code that has a lot of very complex conditional logic in a method? Lots of checks and early exits, or worse maybe some deeply nested if statements or something? Pattern matching can potentially help make that sort of code much more readable and understandable."

InfoQ Weekly Newsletter

Join a community of over 250 K senior developers by signing up for our newsletter. If you are based in the EEA, please contact us so we can provide you with the protections afforded to you under EEA protection laws.

InfoQ Weekly Newsletter

Join a community of over 250 K senior developers by signing up for our newsletter. If you are based in the EEA, please contact us so we can provide you with the protections afforded to you under EEA protection laws.

Is your profile up-to-date? Please take a moment to review and update.

Email Address

Note: If updating/changing your email, a validation request will be sent

Company name:

Keep current company name

Update Company name to:

Company role:

Keep current company role

Update company role to:

Company size:

Keep current company Size

Update company size to:

Country/Zone:

Keep current country/zone

Update country/zone to:

State/Province/Region:

Keep current state/province/region

Update state/province/region to:

Subscribe to our newsletter?

Subscribe to our architect newsletter?

Subscribe to our industry email notices?

By subscribing to this email, we may send you content based on your previous topic interests. See our privacy notice for details.

You will be sent an email to validate the new email address. This pop-up will close itself in a few moments.

We notice you're using an ad blocker

We understand why you use ad blockers. However to keep InfoQ free we need your support. InfoQ will not provide your data to third parties without individual opt-in consent. We only work with advertisers relevant to our readers. Please consider whitelisting us.