BLOG

C# 8 & Visual Studio 2019

Sept. 29, 2020

/

Wilson Chen

The productivity features

TOC

  • What Are .NET Framework & .NET Core Anyway?
  • .NET 5.0 Is The New .NET & Why
  • Visual Studio Versions
  • C# Versions
  • C# 7
  • C# 8
  • Visual Studio Productivity

What Is .NET Framework?

  • .NET is free, cross-platform, open source developer platform from Microsoft, initially version released in 2002

enter image description here

.NET Frameworks

  • .NET has quite a few implementations:
  • .NET Framework, which is tightly coupled with Windows system
  • Mono, runs on Linux & Windows
  • .NET Micro Framework
  • .NET Compact Framework
  • Silverlight: Flash counterpart in web page, deprecated
  • DNA (.NET Anywhere)

And How About .NET Core?

  • .NET Core is the successor to .NET Framework, which was first released in 2016
  • Rebuilt of .NET Framework from the ground up
  • Modularized, all package based (think node packages)
  • Much better performance

.NET 5.0 Is The New .NET

  • .NET Framework 4.8 is end of line
  • .NET Core 3.x is end of line
  • .NET 5.0 Is The New .NET

Why .NET 5.0

  • Currently .NET Core 3.x is the latest version
  • But we already have .NET Framework 4.8
  • So, just skip 1 version to call it .NET 5.0, both .NET Framework & .NET Core will be superseded

C#

enter image description here

  • C# is the main programming language on .NET, it is called c-sharp (think (C++) ++ )
  • There are also other languages like VB.NET etc.

C# Versions

  • The latest public version is 8
  • Version 9 is in preview with .NET 5
  • Let's dig in C# 7 / 8 first

Development Tools

There are heaps of development tools for C#:

  • Visual Studio (Windows only)
  • Visual Studio Code (All platforms)
  • Visual Studio For Mac (MacOS only)
  • JetBrains Rider

Visual Studio is the most powerful one. The community version is free for non-commercial company.

Visual Studio

VS here

You can download latest version of Visual Studio Community version for free here: https://visualstudio.microsoft.com/

Visual Studio Code

VS Code

You can download latest version of Visual Studio Code for free here: https://code.visualstudio.com/

Visual Studio For Mac

VS Mac

You can download latest version of Visual Studio For Mac for free here: https://visualstudio.microsoft.com/vs/mac/

Before C# 7

  • Advanced string literal
  • Exception filter
  • nameof()

Advanced String Literal

In the past, we have composite formatting:

Console.WriteLine("Hello, {0}! Today is {1}, it's {2:HH:mm} now.", name, date.DayOfWeek, date);

Now we can do this with string interpolation. The $ special character identifies a string literal as an interpolated string:

Console.WriteLine($"Hello, {name}! Today is {date.DayOfWeek}, it's {date:HH:mm} now.");

And of course, with advanced formatting:

Console.WriteLine($"On {date:d}, the price of {item.Name} was {item.Price:C2} per {item.perPackage} items");

Exception Filter

Exception Filters are clauses that determine when a given catch clause should be applied:

WebRequestHandler webRequestHandler = new WebRequestHandler();
webRequestHandler.AllowAutoRedirect = false;
using (HttpClient client = new HttpClient(webRequestHandler))
{
  var stringTask = client.GetStringAsync("https://docs.microsoft.com/en-us/dotnet/about/");
  try
  {
    var responseText = await stringTask;
    return responseText;
  }
  catch (System.Net.Http.HttpRequestException e) when (e.Message.Contains("301"))
  {
    return  "Site Moved";
  }
}

nameof()

The nameof expression evaluates to the name of a symbol, so that you don't need to hardocode it as string, and that will help code rectoring logic:

if (IsNullOrWhiteSpace(lastName))
  throw new ArgumentException(message: "Cannot be blank", paramName: nameof(lastName));

C# 7

  • out var
  • discard
  • tuple types & deconstruct
  • local function
  • expression bodied function / constructor / prop
  • switch pattern matching (exception filter ring bell?)
  • default literal expression
  • numeric literal

out var

You can now declare out variables in the argument list of a method call, rather than writing a separate declaration statement.

Before:

if (int.TryParse(input, out  int result))
  Console.WriteLine(result);
else
  Console.WriteLine("Could not parse input");

Now:

if (int.TryParse(input, out var result))
  Console.WriteLine(result);
else
  Console.WriteLine("Could not parse input");

Discard

If you call a method with out parameters but don't want to define a variable as you don't care, use discards. A discard is a write-only variable whose name is _ (the underscore character). A discard is like an unassigned variable:

public static void Main()
    {
        var (_, _, _, pop1, _, pop2) = QueryCityDataForYears("New York City", 1960, 2010);

        Console.WriteLine($"Population change, 1960 to 2010: {pop2 - pop1:N0}");
    }

    private static (string, double, int, int, int, int) QueryCityDataForYears(string name, int year1, int year2)
    {
        int population1 = 0, population2 = 0;
        double area = 0;

        if (name == "New York City")
        {
            area = 468.48;
            if (year1 == 1960)
            {
                population1 = 7781984;
            }
            if (year2 == 2010)
            {
                population2 = 8175133;
            }
            return (name, area, year1, population1, year2, population2);
        }

        return ("", 0, 0, 0, 0, 0);
    }

Tuple Types & Deconstruct

If you need a simple structure containing more than one data element, use tuples, which are lightweight data structures that contain multiple fields to represent the data members.

(string Alpha, string Beta) namedLetters = ("a", "b");
Console.WriteLine($"{namedLetters.Alpha}, {namedLetters.Beta}");

With name:

var alphabetStart = (Alpha: "a", Beta: "b");
Console.WriteLine($"{alphabetStart.Alpha}, {alphabetStart.Beta}");

Deconstruct:

var (Alpha, Beta) namedLetters = ("a", "b");
Console.WriteLine($"{Alpha}, {Beta}");

Local Function

Local method is only accessible from the context in which it is declared.

public static IEnumerable<char> AlphabetSubset3(char start, char end)
{
    if (start < 'a' || start > 'z')
        throw new ArgumentOutOfRangeException(paramName: nameof(start), message: "start must be a letter");
    if (end < 'a' || end > 'z')
        throw new ArgumentOutOfRangeException(paramName: nameof(end), message: "end must be a letter");

    if (end <= start)
        throw new ArgumentException($"{nameof(end)} must be greater than {nameof(start)}");

    return alphabetSubsetImplementation();

    IEnumerable<char> alphabetSubsetImplementation()
    {
        for (var c = start; c < end; c++)
            yield return c;
    }
}

Expression Bodied Function / Constructor / Prop

An expression-bodied member can be used with 1 liner code for both methods and read-only properties.

Readonly property:

public string FullName => $"{FirstName} {LastName}";
// Expression-bodied constructor
public ExpressionMembersExample(string label) => this.Label = label;

// Expression-bodied finalizer
~ExpressionMembersExample() => Console.Error.WriteLine("Finalized!");

private string label;

// Expression-bodied get / set accessors.
public string Label
{
    get => label;
    set => this.label = value ?? "Default label";
}

Switch Pattern Matching (Exception Filter Ring Bell?)

The switch statement was a pattern expression which supports the constant pattern, then later added support for numbers and string:

public static string GenerateMessage(params string[] parts)
{
    switch (parts.Length)
    {
        case 0:
            return "No elements to the input";
        case 1:
            return $"One element: {parts[0]}";
        case 2:
            return $"Two elements: {parts[0]}, {parts[1]}";
        default:
            return $"Many elements. Too many to write";
    }
}

But now, it supports any type:

public static double ComputeAreaModernSwitch(object shape)
{
    switch (shape)
    {
        case Square s:
            return s.Side * s.Side;
        case Circle c:
            return c.Radius * c.Radius * Math.PI;
        case Rectangle r:
            return r.Height * r.Length;
        default:
            throw new ArgumentException(
                message: "shape is not a recognized shape",
                paramName: nameof(shape));
    }
}

And even with when clause:

public static double ComputeArea_Version4(object shape)
{
    switch (shape)
    {
        case Square s when s.Side == 0:
        case Circle c when c.Radius == 0:
        case Triangle t when t.Base == 0 || t.Height == 0:
        case Rectangle r when r.Length == 0 || r.Height == 0:
            return 0;

        case Square s:
            return s.Side * s.Side;
        case Circle c:
            return c.Radius * c.Radius * Math.PI;
        case Triangle t:
            return t.Base * t.Height / 2;
        case Rectangle r:
            return r.Length * r.Height;
        default:
            throw new ArgumentException(
                message: "shape is not a recognized shape",
                paramName: nameof(shape));
    }
}

Default Literal Expression

In the past, we initialize a variable with a default value:

Func<string, bool> whereClause = default(Func<string, bool>);

Now, we can simply use the default operator:

Func<string, bool> whereClause = default;

Numeric Literal

There are two new features to write numbers in the most readable fashion for the intended use: binary literals, and digit separators.

public const int SixtyFour = 0b0100_0000;
public const int OneHundredThousand = 100_000;

C# 8

  • var using
  • readonly member
  • switch expression
  • index / range
  • null-coalescing assignment (?? ring a bell?)

var using

A using declaration is a variable declaration preceded by the using keyword. The compiler will dispose the variable at the end of the enclosing scope:

static int WriteLinesToFile(IEnumerable<string> lines)
{
    using var file = new System.IO.StreamWriter("WriteLines2.txt");
    // Notice how we declare skippedLines after the using statement.
    int skippedLines = 0;
    foreach (string line in lines)
    {
        if (!line.Contains("Second"))
        {
            file.WriteLine(line);
        }
        else
        {
            skippedLines++;
        }
    }
    // Notice how skippedLines is in scope here.
    return skippedLines;
    // file is disposed here
}

Readonly Member

You can mark a member readonly, like the Distance property below:

public struct Point
{
    public double X { get; set; }
    public double Y { get; set; }
    public double Distance => Math.Sqrt(X * X + Y * Y);

    public override string ToString() =>
        $"({X}, {Y}) is {Distance} from the origin";
}

Switch Expression

Existing switch is a statement, but now we have expression:

Imagine we have an enum:

public enum Rainbow
{
    Red,
    Orange,
    Yellow,
    Green,
    Blue,
    Indigo,
    Violet
}

In the past we need to do like this:

public static RGBColor FromRainbowClassic(Rainbow colorBand)
{
    switch (colorBand)
    {
        case Rainbow.Red:
            return new RGBColor(0xFF, 0x00, 0x00);
        case Rainbow.Orange:
            return new RGBColor(0xFF, 0x7F, 0x00);
        case Rainbow.Yellow:
            return new RGBColor(0xFF, 0xFF, 0x00);
        case Rainbow.Green:
            return new RGBColor(0x00, 0xFF, 0x00);
        case Rainbow.Blue:
            return new RGBColor(0x00, 0x00, 0xFF);
        case Rainbow.Indigo:
            return new RGBColor(0x4B, 0x00, 0x82);
        case Rainbow.Violet:
            return new RGBColor(0x94, 0x00, 0xD3);
        default:
            throw new ArgumentException(message: "invalid enum value", paramName: nameof(colorBand));
    };
}

Now we can:

public static RGBColor FromRainbow(Rainbow colorBand) =>
    colorBand switch
    {
        Rainbow.Red    => new RGBColor(0xFF, 0x00, 0x00),
        Rainbow.Orange => new RGBColor(0xFF, 0x7F, 0x00),
        Rainbow.Yellow => new RGBColor(0xFF, 0xFF, 0x00),
        Rainbow.Green  => new RGBColor(0x00, 0xFF, 0x00),
        Rainbow.Blue   => new RGBColor(0x00, 0x00, 0xFF),
        Rainbow.Indigo => new RGBColor(0x4B, 0x00, 0x82),
        Rainbow.Violet => new RGBColor(0x94, 0x00, 0xD3),
        _              => throw new ArgumentException(message: "invalid enum value", paramName: nameof(colorBand)),
    };

Index / Range

Index and range can be used to easily manipulate array. For array, the 0 index is the same as sequence[0]. The ^0 index is the same as sequence[sequence.Length]. Sequence[^0] throws an exception, just as sequence[sequence.Length] does. For any number n, the index ^n is the same as sequence.Length - n.

var words = new string[]
{
                // index from start    index from end
    "The",      // 0                   ^9
    "quick",    // 1                   ^8
    "brown",    // 2                   ^7
    "fox",      // 3                   ^6
    "jumped",   // 4                   ^5
    "over",     // 5                   ^4
    "the",      // 6                   ^3
    "lazy",     // 7                   ^2
    "dog"       // 8                   ^1
};              // 9 (or words.Length) ^0

To get the last word:

Console.WriteLine($"The last word is {words[^1]}");

To get a range:

Range phrase = 1..4;
var text = words[phrase];

Null-Coalescing Assignment (?? ring a bell?)

We can use the ??= operator to assign the value of its right-hand operand to its left-hand operand only if the left-hand operand evaluates to null.

List<int> numbers = null;
int? i = null;

numbers ??= new List<int>();
numbers.Add(i ??= 17);
numbers.Add(i ??= 20);

Console.WriteLine(string.Join(" ", numbers));  // output: 17 17
Console.WriteLine(i);  // output: 17

Visual Studio Productivity

  • Everything is searchable
  • Quick launch: Ctrl+Q
  • Search solution: Ctrl+;
  • Search work item: Ctrl+'
  • Find in find, multiple search results
  • Quick search of everything: Ctrl+T , type anything, much quicker than search
  • Refactor anywhere: Ctrl+.

Visual Studio Productivity

It's not just an editor!

  • Quick code snippets: you can manage and use code snippet and quickly insert, like input ctor and then double press Tab
  • Copy the quick way: Just click copy without selecting the whole line!
  • Duplicate: Ctrl+D
  • Column editing: Hold Alt key then start selecting text, then change the text you like for all selection!
  • Lines move around: Alt+Arrow Up/Down to move the current line / selected lines up or down
  • Tidy up usings: right click -> Rename/remove & sort usings
  • Surround it: Select some text, Ctrl+S, you can surround the text with try/catch etc.

Visual Studio Productivity

  • Windows, the more the merrier: Menu -> Window
  • File split
  • Multi docs split
  • Vertical / horizontal
  • Paste special (from json/xml to POCO): Menu -> Edit -> Paste Special

Visual Studio Productivity

  • Advanced file editing
  • Structural layout: Menu -> Edit -> Outlining
  • Format it all: Menu -> Edit -> Advanced -> Format Document
  • Terminal: Menu -> View -> Terminal

Visual Studio Quality & Performance

So, what's next?

  • C# Interactive
  • Advanced debugging
  • Advanced profiling / diagnostics
  • Writing high quality & performance C# code

TAGS

.NET C sharp

Share

Contact Us

Icon

Address
Level 8
11-17 York Street
Sydney NSW 2000

Icon

Phone Number
+61 2 8294 8067

This site is protected by reCAPTCHA and the Google Privacy Policy and Terms of Service apply.

© 2017-2024 Darumatic Pty Ltd. All Rights Reserved.