Book Image

ASP.NET Core 2 High Performance - Second Edition

By : James Singleton
Book Image

ASP.NET Core 2 High Performance - Second Edition

By: James Singleton

Overview of this book

The ASP.NET Core 2 framework is used to develop high-performance and cross-platform web applications. It is built on .NET Core 2 and includes significantly more framework APIs than version 1. This book addresses high-level performance improvement techniques. It starts by showing you how to locate and measure problems and then shows you how to solve some of the most common ones. Next, it shows you how to get started with ASP.NET Core 2 on Windows, Mac, Linux, and with Docker containers. The book illustrates what problems can occur as latency increases when deploying to a cloud infrastructure. It also shows you how to optimize C# code and choose the best data structures for the job. It covers new features in C# 6 and 7, along with parallel programming and distributed architectures. By the end of this book, you will be fixing latency issues and optimizing performance problems, but you will also know how this affects the complexity and maintenance of your application. Finally, we will explore a few highly advanced techniques for further optimization.
Table of Contents (20 chapters)
Title Page
Credits
Foreword
About the Author
About the Reviewer
www.PacktPub.com
Customer Feedback
Preface
3
Setting Up Your Environment
4
Measuring Performance Bottlenecks

New C# features


It's not just the frameworks and libraries that have been worked on. The underlying language also had some nice new features added. We will focus on C# here as it is the most popular language for the Common Language Runtime (CLR). Other options include Visual Basic and the functional programming language F#.

C# is a great language to work with, especially when compared to a language such as JavaScript. Although JavaScript is great for many reasons (such as its ubiquity and the number of frameworks available), the elegance and design of the language is not one of them. We will cover JavaScript later in the book.

Many of these new features are just syntactic sugar, which means they don't add any new functionality. They simply provide a more succinct and easier-to-read way of writing code that does the same thing.

C# 6

Although the latest version of C# is 7, there are some very handy features in C# 6 that often go underused. Also, some of the new additions in 7 are improvements on features added in 6 and would not make much sense without any context. We will quickly cover a few features of C# 6 here, in case you are unaware of how useful they can be.

String interpolation

String interpolation is a more elegant and easier-to-work-with version of the familiar string format method. Instead of supplying the arguments to embed in the string placeholders separately, you can now embed them directly in the string. This is far more readable and less error-prone.

Let's demonstrate this with an example. Consider the following code that embeds an exception in a string:

catch (Exception e)
{
    Console.WriteLine("Oh dear, oh dear! {0}", e);
}

This embeds the first (and in this case only) object in the string at the position marked by zero. It may seem simple, but it quickly gets complex if you have many objects and want to add another at the start. You then have to correctly renumber all the placeholders.

Instead, you can now prefix the string with a dollar character and embed the object directly in it. This is shown in the following code that behaves the same as the previous example:

catch (Exception e)
{
    Console.WriteLine($"Oh dear, oh dear! {e}");
}

The ToString() method on an exception outputs all the required information, including the name, message, stack trace, and any inner exceptions. There is no need to deconstruct it manually; you may even miss things if you do.

You can also use the same format strings as you are used to. Consider the following code that formats a date in a custom manner:

Console.WriteLine($"Starting at: {DateTimeOffset.UtcNow:yyyy/MM/dd HH:mm:ss}");

When this feature was being built, the syntax was slightly different. So, be wary of any old blog posts or documentation that may not be correct.

Null conditional

The null conditional operator is a way of simplifying null checks. You can now place an inline check for null rather than use an if statement or ternary operator. This makes it easier to use in more places and will hopefully help you avoid the dreaded null reference exception.

You can avoid doing a manual null check, as in the following code:

int? length = (null == bytes) ? null : (int?)bytes.Length;

This can now be simplified to the following statement by adding a question mark:

int? length = bytes?.Length;

Exception filters

You can filter exceptions more easily with the when keyword. You no longer need to catch every type of exception that you are interested in and then filter it manually inside the catch block. This is a feature that was already present in VB and F#, so it's nice that C# has finally caught up.

There are some small benefits to this approach. For example, if your filter is not matched, then the exception will still be caught by other catch blocks in the same try statement. You also don't need to remember to rethrow the exception to avoid it from being swallowed. This helps with debugging, as Visual Studio will no longer break as it would when you use throw.

For example, you could check to see whether there is a message in the exception and handle it differently, as shown here:

catch (Exception e) when (e?.Message?.Length > 0)

When this feature was in development, a different keyword (if) was used. So be careful of any old information online.

One thing to keep in mind is that relying on a particular exception message is fragile. If your application is localized, then the message may be in a different language to what you expect. This holds true outside of exception filtering too.

Asynchronous availability

Another small improvement is that you can use the await keyword inside catch and finally blocks. This was not initially allowed when this incredibly useful feature was added to C# 5. There is not a lot more to say about this. The implementation is complex, but you don't need to worry about this unless you're interested in the internals. From a developer's point of view, it just works, as in this simple example:

catch (Exception e) when (e?.Message?.Length > 0)
{
    await Task.Delay(200);
}

This feature has been improved in C# 7, so read on. You will see async and await used a lot throughout this book. Asynchronous programming is a great way of improving performance and not just from within your C# code.

Expression bodies

Expression bodies allow you to assign an expression to a method or getter property using the lambda arrow operator (=>), which you may be familiar with from fluent LINQ syntax. You no longer need to provide a full statement or method signature and body. This feature has also been improved in C# 7, so see the examples in the next section.

For example, a getter property can be implemented like so:

public static string Text => $"Today: {DateTime.Now:o}";

A method can be written in a similar way, such as the following example:

private byte[] GetBytes(string text) => Encoding.UTF8.GetBytes(text);

C# 7

The most recent version of the C# language is 7, and there are yet more improvements to readability and ease of use. We'll cover a subset of the more interesting changes here.

Literals

There are a couple of minor additional capabilities and readability enhancements when specifying literal values in code. You can specify binary literals, which means you don't have to work out how to represent them using a different base anymore. You can also put underscores anywhere within a literal to make it easier to read the number. The underscores are ignored but allow you to separate digits into convention groupings. This is particularly well suited to the new binary literal as it can be very verbose, listing out all those zeros and ones.

Take the following example that uses the new 0b prefix to specify a binary literal that will be rendered as an integer in a string:

Console.WriteLine($"Binary solo! {0b0000001_00000011_000000111_00001111}");

You can do this with other bases too, such as this integer, which is formatted to use a thousands separator:

Console.WriteLine($"Over {9_000:#,0}!"); // Prints "Over 9,000!"

Tuples

One of the big new features in C# 7 is support for tuples. Tuples are groups of values, and you can now return them directly from method calls. You are no longer restricted to returning a single value. Previously, you could work around this limitation in a few suboptimal ways, including creating a custom complex object to return, perhaps with a Plain Old C# Object (POCO) or Data Transfer Object (DTO), which are the same thing. You could have also passed in a reference using the ref or out keyword, which are still not great although there are improvements to the syntax.

There was System.Tuple in C# 6, but it wasn't ideal. It was a framework feature, rather than a language feature, and the items were only numbered and not named. With C# 7 tuples, you can name the objects and they make a great alternative to anonymous types, particularly in LINQ query expression lambda functions. As an example, if you only want to work on a subset of the data available, perhaps when filtering a database table with an O/RM, such as Entity Framework, then you could use a tuple for this.

The following example returns a tuple from a method. You may need to add the System.ValueTuple NuGet package for this to work:

private static (int one, string two, DateTime three) GetTuple()
{
    return (one: 1, two: "too", three: DateTime.UtcNow);
}

You can also use tuples in string interpolation and all the values will be rendered, as shown here:

Console.WriteLine($"Tuple = {GetTuple()}");

Out variables

If you want to pass parameters to a method for modification, then you always need to declare them first. This is no longer necessary, and you can simply declare the variables at the point you pass them in. You can also declare a variable to be discarded, using an underscore. This is particularly useful if you don't want to use the returned value, for example, in some of the try parse methods of the native framework data types.

Here, we parse a date without declaring the dt variable first:

DateTime.TryParse("2017-08-09", out var dt);

In this example, we test for an integer, but we don't care what it is:

var isInt = int.TryParse("w00t", out _);

References

You can now return values by reference from a method as well as consume them. This is a little like working with pointers in C but safer. For example, you can only return references that were passed to the method, and you can't modify references to point to a different location in memory. This is a very specialist feature, but in certain niche situations, it can dramatically improve performance.

Consider the following method:

private static ref string GetFirstRef(ref string[] texts)
{
    if (texts?.Length > 0)
    {
        return ref texts[0];
    }
    throw new ArgumentOutOfRangeException();
}

You could call this method like so, and the second console output line would appear differently (one instead of 1):

var strings = new string[] { "1", "2" };
ref var first = ref GetFirstRef(ref strings);
Console.WriteLine($"{strings?[0]}"); // 1
first = "one";
Console.WriteLine($"{strings?[0]}"); // one

Patterns

The other big addition is you can now match patterns in C# 7 using the is keyword. This simplifies testing for null and matching against types, among other things. It also lets you easily use the cast value. This is a simpler alternative to using full polymorphism (where a derived class can be treated as a base class and override methods). However, if you control the code base and are able to make use of polymorphism properly, then you should still do this and follow good object-oriented programming (OOP) principles.

In the following example, pattern matching is used to parse the type and value of an unknown object:

private static int PatternMatch(object obj)
{
    if (obj is null)
    {
        return 0;
    }
    if (obj is int i)
    {
        return i++;
    }
    if (obj is DateTime d ||
       (obj is string str && DateTime.TryParse(str, out d)))
    {
        return d.DayOfYear;
    }
    return -1;
}

You can also use pattern matching in the case of a switch statement, and you can switch on non-primitive types, such as custom objects.

More expression bodies

Expression bodies are expanded from the offering in C# 6, and you can now use them in more places, for example, as object constructors and property setters. Here, we extend our previous example to include the setting up of the value on the property we were previously just reading:

private static string text;
public static string Text
{
    get => text ?? $"Today: {DateTime.Now:r}";
    set => text = value;
}

More asynchronous improvements

There have been some small improvements to what async methods can return and, although small, they could offer big performance gains in certain situations. You no longer have to return a task which can be beneficial if the value is already available. This can reduce the overhead of using async methods and creating a task object.