BT

Build 2018: The Future of C#

| by Jonathan Allen Follow 612 Followers on May 21, 2018. Estimated reading time: 5 minutes |

Number one on the list of future C# features is Nullable Reference Types. We first covered this last year, but to briefly recap: all reference variables, parameters, and fields are going to be non-nullable by default. Then, like value types, if you want anything to be nullable you must explicitly indicate that by appending a question mark (?) to the type name.

This will be an optional feature and the current thought is nullable reference types will be turned off for existing projects that are being upgraded to C# 8. For new projects, Microsoft is inclined to turn the feature on by default.

The warnings will be further subdivided into potential errors and merely cosmetic warnings. For example, if p.MiddleName is a string?, then this line would be a cosmetic warning:

string middleName = p.MiddleName;

Since the danger doesn’t occur until the value is dereferenced, this assignment to a local variable isn’t really a problem. So, you can disable the warning on legacy code to cut down on the number of false positives.

Likewise, libraries predating this feature won’t trigger warnings because the compiler doesn’t know whether or not a given parameter should be treated as nullable.

A preview of nullable reference types is available on GitHub.

Switch Expressions

Switch blocks are commonly used to simply return a single value. In this common scenario, the syntax is quite verbose compared to what is being done. Consider this example using pattern matching:

static string M(Person person)
{
    switch (person)
    {
        case Professor p:
            return $"Dr. {p.LastName}";
        case Studen s:
            return $"{s.FirstName} {s.LastName} ({s.Level})";
        default:
            return $"{person.FirstName} {person.LastName}";
    }
}

Under the new proposal, the repetitive case and return statements can be eliminated. This results in this newer, more compact syntax.

static string M(Person person)
{
    return person switch
    {
        Professor p => $"Dr. {p.LastName}",
        Student s => $"{s.FirstName} {s.LastName} ({s.Level})",
        _ => $"{person.FirstName} {person.LastName}"
    };
}

The exact syntax is still under debate. For example, it hasn’t been decided if this feature will use => or : to separate the pattern expression from the return value.

Property Pattern Matching

Currently it is possible to perform property level pattern matching via the when clause.

case Person p when p.LastName == "Cambell" : return $"{p.FirstName} is not enrolled";

With “property patterns” this is reduced to

case Person { LastName: "Cambell"} p : return $"{p.FirstName} is not enrolled";

A small gain, but it does make the code clearer and removes a redundant instance of the variable name.

Extracting Properties from Patterns

Taking it a step further, you can also declare variables in the pattern.

case Person { LastName: "Cambell", FirstName: var fn} p : return $"{fn} is not enrolled";

Deconstructors in Patterns

A deconstructor is used to break down an object into its constituent parts. It is primarily used for multiple assignment from a tuple. With C# 7.3, you can also use deconstruction with pattern matching.

In this next example, the Person class deconstructs to {FirstName, MiddleName, LastName}. Since we are not using MiddleName, an underscore is used as a placeholder for the skipped property.

case Person ( var fn, _, "Cambell" ) p : return $"{fn} is not enrolled";

Recursive Patterns

For our next pattern, let us say the class Student deconstructs into {FirstName, MiddleName, LastName, Professor}. We can decompose both the Student object and its child Professor object at the same time.

case Student ( var fn, _, "Cambell", var (_, _, ln) ) p : return $"{fn} is enrolled in {ln}’s class";

In this line of code, we:

  1. Extracted student.FirstName into fn
  2. Skipped student.MiddleName
  3. Matched student.LastName to “Cambell”
  4. Skipped student.Professor.FirstName and student.Professor.MiddleName
  5. Extracted student.Professor.LastName into ln

A preview of recursive pattern matching is available on GitHub.

Index Expressions

A range expression eliminates much of the verbose (and error prone) syntax for dealing with array-like data structures.

The hat operator (^) counts from the end of the list. For example, to get the last value in a string you could write,

var lastCharacter = myString[myString.Length-1];

or simply

var lastCharacter = myString[^1];

This works with computed values such as

Index nextIndex = ^(x + 1);
var nextChar = myString[nextIndex]

Range Expressions

Closely related to index expressions are range expressions, denoted with the (..) operator. Here is a simple example that gets the first three characters in a string.

var s = myString.Substring[0..2];

This can be combined with an index expression. In the next line, we skip the first and last character.

var s = myString.Substring(1..^1);

This also works with Span<T>.

Span<int> slice = myArray[4…8];

Note the first number is inclusive and the second number is exclusive. So, the variable slice would have elements 4 thru 7.

To get a slice with all elements starting at an index, there are two options.

Span<int> rest = myArray[8..];
Span<int> rest = myArray[8..^0];

Likewise, you can omit the first index.

Span<int> firstFour = myArray[..4];

In case you are wondering, this syntax was heavily inspired by Python. The major difference is C# can’t use -1 for indexing from the end of an array because that already has a meaning in .NET arrays. So instead we get the ^1 syntax.

A preview of indexes and ranges is available on GitHub.

IAsyncDisposable

As the name implies, this interface allows objects to expose a DisposeAsync method. In conjunction with this is the new syntax “using async” which works just like a normal using block with the added ability to await on a dispose call.

Asynchronous Enumerators

Like IEnumerable<T>, IAsyncEnumerable<T> will allow you to enumerate a finite list of unknown length. The matching enumerator looks slightly different though. It exposes two methods:

Task<bool> WaitForNextAsync();
T TryGetNext(out bool success);

An interesting feature of this interface is it allows you to read data in batches. You call TryGetNext for each item in the batch. When that returns success=false, you then call WaitForNextAsync to fetch a new batch.

The reason this is important is most data comes to the application in either batches or streamed over the network. When you call TryGetNext, the data will already be available most of the time. Allocating a Task object would be wasteful, but if you do run out of data in the input buffer, you still want to be able to asynchronously wait for more.

Ideally you wouldn’t use these interfaces directly very often. Instead Microsoft would like you to use the “foreach await” syntax known as an asynchronous iterator, which we previewed last year. This will handle calling the synchronous or asynchronous method as necessary.

Default Interface Methods

This controversial feature inspired by Java is still being considered for C# 8. Briefly, it allows you to evolve an interface by adding new methods with matching implementations. This way the new method won’t break backwards compatibility.

Records

Records are a syntax for quickly creating immutable classes. We first saw this proposal in 2014. The current example looks like this:

class Person(string FirstName, string LastName);

Basically, it’s a class defined entirely by a constructor’s signature. All of the properties and methods you would expect from an immutable class are automatically generated.

Rate this Article

Adoption Stage
Style

Hello stranger!

You need to Register an InfoQ account or or login to post comments. But there's so much more behind being registered.

Get the most out of the InfoQ experience.

Tell us what you think

Allowed html: a,b,br,blockquote,i,li,pre,u,ul,p

Email me replies to any of my messages in this thread

It used to be so nice by Cameron Purdy

C# used to be a beautiful language (with some warts).

Why does Microsoft keep whacking it with the ugly stick?

Re: It used to be so nice by Néstor Sánchez

Come on, it's becoming with more power and easier (less typing) than ever.
If you want something simpler to the sight, go for Visual Basic.

C# becomes ever more idiomatic by Dan Sutton

It's fascinating in how many different ways you can now express the same thing in this language: I'm waiting for slang to develop...
...the recursive pattern matching in Case statements is very interesting.... but be careful! The language is in danger of becoming write-only!

Terrible Switch by Michael McCutcheon

The switch statement is already very simple and easy to understand. Replacing it with another syntax that is slightly simpler gains nothing and just makes the code harder to read. Replacing something easy with something *slightly* easier is of little value. It's like they're trying to change things for change sake. Just leave it already.

The switch statement stuff is just so bad... by Andrew Witte

wtf is this "return person switch"??
At least make it fit the C# syntax and style please like "return switched (person)" or something more parsable...

Re: The switch statement stuff is just so bad... by Andrew Witte

Please make it something like (C# Style)

return match (person)
{
case Professor: $"Dr. {person.LastName}";
};

WTF were they thinking. by Francisco Chavez

Nullable references? Really? WTF? The thing that made nullable structs usesful is that it gave us the ability to assign null to struct based attributes and parameters. References already are nullable. This isn't adding any new capabilities to the language; this is making us jump through extra hoops just to use something that's already there.

Re: WTF were they thinking. by Jonathan Allen

The new capability is the ability to mark reference typed variables as non-nullable.

Re: Terrible Switch by Jonathan Allen

In my opinion the current switch statement is overly verbose. At the very least they need to eliminate the need for break statements and allow for multiple cases with having to repeat the word "case" over and over again.

Re: WTF were they thinking. by Francisco Chavez

The new capability is the ability to mark reference typed variables as non-nullable.


Having the ability to make a reference type a non-nullable would be a new capability, but they specifically said that they will be non-nullable by default. If they are non-nullable by default, then we are not the ones that are making them non-nullable. You can not make something non-nullable if it's non-nullable to begin with. I now declare your name to be Jonathan Allen. Oh, no, wait, you already are Jonathan Allen.

Sure, there are plenty of cases where a NULL value can get a person in trouble, and inserting a null check where there should never be a null just introduces branching statements that are technically never needed. I wouldn't mind being able to make a reference into a non-nullable, but what they're doing is forcing me to make my references into nullables. Sure, it's only one extra character, but they are still forcing me to do more work than before and calling that an improvement for my benefit. Introducing the 'var' keyword was an improvement, giving us the Parallel library was an improvement, but this is BS.

To make matters worse, they're using a syntax that isn't recognized by most programmers that I know. I've met multiple programmers that have no idea what that question mark behind a struct based variable means, and when they try to search for it, all they get are links to the ternary operator. They are doubling down on something that causes quite a few bugs for experienced people that are coming in from different languages and introducing a change in the language that brakes backwards capability at the same time.

Re: WTF were they thinking. by Jonathan Allen

Microsoft's argument is two-fold

1. The vast majority of variables are intended to not store nulls. So that should be the default.

2. This design makes it consistent with value types.

They did experiment with leaving variables as nullable by default and requiring the use of the bang operator (!) to make them non-nullable.

***

Also note that this is an optional feature. It doesn't break backwards compatibility if you don't turn it on.

multi-paradigm by M Whitener

C# will always be a multi-paradigm language. As such, expect many strange and wonderful tricks and syntax enhancements.

Retro-purist complainers here should stick with Java ... or C. Actually I don't remember any purists in the C world, so I guess that won't work for them.

Re: multi-paradigm by M Whitener

As purist OO (really, Class Orientation) completes its nauseating arc into obscurity, I hope I live long enough to see the best possible implementation of Traits in C# / or the ascendance of a proper successor language.

But the discussion on this page proves that C# has been its own successor, so far.

Long live F# by Githin George

All this features are coming from F#. Better to use f# and a functional first language.

Allowed html: a,b,br,blockquote,i,li,pre,u,ul,p

Email me replies to any of my messages in this thread

Allowed html: a,b,br,blockquote,i,li,pre,u,ul,p

Email me replies to any of my messages in this thread

14 Discuss

Login to InfoQ to interact with what matters most to you.


Recover your password...

Follow

Follow your favorite topics and editors

Quick overview of most important highlights in the industry and on the site.

Like

More signal, less noise

Build your own feed by choosing topics you want to read about and editors you want to hear from.

Notifications

Stay up-to-date

Set up your notifications and don't miss out on content that matters to you

BT