In previous articles, we covered C# 8 asynchronous streams, C# 8 pattern matching, C# 8 default interface methods, and C# 8 nullable reference types. In this final article, we'll look at static
local functions, indices and ranges, and using
declarations.
static
local functions
C# 7 introduced local functions, which are defined and used inside the caller function. Such functions can change their caller's local variables. To disallow this possibility and require explicit passing of all arguments, local functions can now be marked as static
:
void Bar() { int i = 0; Foo(); static void Foo() { i = 3; // CS8421: A static local function cannot contain a reference to 'i' } }
Indices and ranges
C# 8 adds a syntax for ranges. This syntax consists of the range operator (..
), which is surrounded by a start and end expression that specifies the index of the first element (which is included) and the index of the last element (which is excluded):
string[] fruits = new string[] { "apple", "banana", "cherry", }; string[] allFruits = fruits[0..3]; string[] allFruits2 = fruits[0..fruits.Length]; string[] allFruits3 = fruits[0..(2 + 1)];
In the examples above, we take the range of all fruits by specifying 0
as the start index and 3
as the end index. The end index is one past the last element because C# indexing is zero-based. Because the end index is not included in the range, we've added one.
It’s also possible to index from the end using the ^
operator. ^0
is the index past the last element, ^1
is the last element, ^2
the element preceding it, and so on.
Here is an example:
string[] lastTwoFruits = fruits[^2..^0];
By default, the start index is 0
, and the end index is ^0
, so one or both might be omitted:
string[] allFruits4 = fruits[..]; string[] skipFirstTwoFruits = fruits[2..];
Reverse indexing can also be used directly:
string lastFruit = fruits[^1];
The C# compiler uses the System.Index
and System.Range
types to represent the range and index. You can support your own types by adding indexers that take these types. Types that already have an indexer that takes an int
and an int Count/Length
property implicitly get support for reverse indexing. Types that additionally include a Slice
method that takes two ints
(assumed offset and count) implicitly get support for taking a range.
The .NET array, string
, and Span
support both indexes and ranges. The List
type supports indexes, but not ranges.
using
declarations
C# has support for disposing of variables with the using
keyword. Previous versions of C# required a block statement that explicitly scoped the lifetime:
using (FileStream fs = File.OpenRead("myfile.txt")) { // explicit block ReadFromStream(fs); }
With C# 8, this block is no longer needed. The compiler will use the declaring scope:
using FileStream fs = File.Open("myfile.txt"); ReadFromStream(fs);
As you can see, this reduces the indentation.
Conclusion
In this article, we’ve looked at three different C# 8 features:
static
local functions, which require explicit passing of all arguments to local functions.- Indices and ranges, which introduce first-class support to denote ranges and perform reverse indexing.
using
declarations, which reduce levels of indentation when working with disposable objects.
C# 8 can be used with the .NET Core 3.1 SDK, which is available on RHEL, Fedora, Windows, macOS, and other Linux distributions.
Last updated: February 24, 2024