DEV Community

Jim Borden
Jim Borden

Posted on

New Features in C# 7.1 and 7.2

I haven't counted exactly but I must have around a decade of .NET experience at this point. I love working with .NET and C#, and I get excited to see new features in the language. The latest major released version of C# is version 7, with two minor releases being 7.1 and 7.2. I'll talk about the new features of 7.1 and 7.2, and then go over some of the features I am excited about for future versions.

C# 7.1

Async Main

Remember a couple versions ago when C# introduced the async and await keywords? I sure do, that was one of the best features I could have asked for. It was also dangerous unless you realize how it works. The async function being awaited would return early until it is ready to pick up at a later point, and the caller would continue as if the function containing the await had finished, unless that caller also had an await. So it's easy to see how you would get "await-infected" all the way up your stack. Then you got to main() which was required to be void and you were out of luck. No more! Now you can have an asynchronous main() that is automatically handled by the CLR. It looks like this:

static async Task<int> Main() or static async Task Main() (which represent asynchronous int and void respectively). Happy asynching!

Default Literal Expressions

At a high level there are two types of objects in C#: structs and classes. The difference being that structs are not allocated on the managed heap and all their memory is simply laid out starting from whatever address they occupy then and there. This means that it does not make sense the have a null struct since null is obviously not on your current stack, and never will be. However, when using generic methods this meant that it was awkward to initialize a variable of generic type T because you didn't know what a suitable "default" value should be. Enter the default(T) syntax. If you write default(<type>) then you will get the compiler chosen default value for whatever type you've chosen. This can get messy though, since nobody wants to look at something like default(Tuple<string, KeyValuePair<string, object>>). Now you can shorten this to simply default without the type argument and it will be inferred from the variable type. A small change but a nice touch!

Inferred Tuple Element Names

C# 7 introduced a fun new way to make tuples. I'm not sure many people liked making a tuple and then remembering what Item1 and Item2 on the tuple was. So instead, now you can write a block like this:

var name = "Jim";
var age = 32;
var pair = (name: name, age: age);

// Now access via pair.name and pair.age
Enter fullscreen mode Exit fullscreen mode

Now this has been improved upon to simply this:

var pair = (name, age);

// Now access via pair.name and pair.age
// automatically named from the variable names
Enter fullscreen mode Exit fullscreen mode

Compiler Options /refout and /refonly

If you remember back to the Portable Class Library (PCL) days, some people required platform specific functionality in order for their library to work, but they wanted their library to be usable from other PCL that targeted the same subset of platforms as they did. They couldn't compile it as a PCL, though, because they used platform specific API that wasn't present in the defined PCL API set. So what they did instead was trick the NuGet client by putting in a shim that did nothing as a PCL library, and also put in the actual assemblies for each platform they supported under their target moniker (e.g. net461, xamarinios, etc). Since NuGet always prefers a platform specific assembly, what would happen is that when they installed it on their top level project NuGet would pull out the correct platform assembly, but when they compiled their PCL it would be compiling against the shim. Since they both had the same version and everything else looked identical the .NET compiled happily overwrote the shim version with the real one at the top level and everything worked out. This was coined "bait and switch."

Well, it was such a good trick that it actually got legitimized. Have you ever wondered what a reference assembly is? It is exactly the "shim" I talked about above. It is pretty much like a header file for C or C++ in that it defines what API is available without actually implementing it. It is used for when a library otherwise cannot target .NET Standard and needs to have a per platform assembly, but still wants to be usable from shared .NET Standard libraries. Now, there are compiler options to automatically generate this reference assembly for you. /refout will specify where to write the automatically generated reference assembly, and also compile the implementation. /refonly will only compile the reference assembly.

C# 7.2

Reference Semantics with Value Types

Reference semantics were introduced in C# 7 as a well to reduce the number of copies made when returning values and such. For example, instead of returning a copy of an item that is inside of an array, you can directly return the same object that is in the array. These modifications have been extended to work with structs and resulted in the following new modifiers:

  • in Specifies that you want to pass a struct by reference, but the caller cannot modify it
  • ref readonly returns a struct by reference as an object that may not be modified
  • readonly struct creates a struct that can never be modified once created
  • ref struct deserves an article of its own, but basically means that it can never be allocated on the managed heap. The main motivation is for the awesome new Span<T> class which also deserves its own article.

Non-Trailing Named Arguments

This one is meant to give meaning to all those literal true false and null objects that you pass in your code. So for example if you have something like

public void Log(bool verbose, string message, object arg)

You could now make the call a little bit clearer by changing the following line to the second line:

Log(true, "Foo", 42);
Log(verbose: true, "Foo", 42);
Enter fullscreen mode Exit fullscreen mode

Leading Underscore in Numeric Literals

This is a very small one that makes a readability improvement. Before when you used a numeric literal, it could not begin with an underscore and so you ended up with 0b0101_0101 which is hard to read. Now you can put in better spacing with 0b_0101_0101

private protected Access Modifier

This one was debated about for years with an all out war emerging in the community. Nobody could agree on what to name it and so in the end it was decided to go with the same as managed C++. This angered some but I don't care because I want to use it. What this new access modifier means is that any element declared with this will be visible to subclasses, but only if those subclasses are in the same assembly as the base class. This is very useful for me in the following situation:

internal sealed class Hidden
{

}

public abstract class Visible
{
    protected Hidden HiddenObj { get; private set; }
}
Enter fullscreen mode Exit fullscreen mode

This will fail to compile because a subclass of Visible has a property it can see, which is of a type it cannot see because Hidden is internal. The only remedy until now was to make the property internal instead of protected which meant lots of noise when dealing with the type inside the assembly. Now with private protected It can be invisible to both other types in the same assembly, and subclasses outside the assembly.

Top comments (0)