Home Programming High-Performance Programming in C# and .NET

High-Performance Programming in C# and .NET

By Jason Alls
books-svg-icon Book
eBook $41.99 $28.99
Print $51.99
Subscription $15.99 $10 p/m for three months
$10 p/m for first 3 months. $15.99 p/m after that. Cancel Anytime!
What do you get with a Packt Subscription?
This book & 7000+ ebooks & video courses on 1000+ technologies
60+ curated reading lists for various learning paths
50+ new titles added every month on new and emerging tech
Early Access to eBooks as they are being written
Personalised content suggestions
Customised display settings for better reading experience
50+ new titles added every month on new and emerging tech
Playlists, Notes and Bookmarks to easily manage your learning
Mobile App with offline access
What do you get with a Packt Subscription?
This book & 6500+ ebooks & video courses on 1000+ technologies
60+ curated reading lists for various learning paths
50+ new titles added every month on new and emerging tech
Early Access to eBooks as they are being written
Personalised content suggestions
Customised display settings for better reading experience
50+ new titles added every month on new and emerging tech
Playlists, Notes and Bookmarks to easily manage your learning
Mobile App with offline access
What do you get with eBook + Subscription?
Download this book in EPUB and PDF formats, plus a monthly download credit
This book & 6500+ ebooks & video courses on 1000+ technologies
60+ curated reading lists for various learning paths
50+ new titles added every month on new and emerging tech
Early Access to eBooks as they are being written
Personalised content suggestions
Customised display settings for better reading experience
50+ new titles added every month on new and emerging tech
Playlists, Notes and Bookmarks to easily manage your learning
Mobile App with offline access
What do you get with a Packt Subscription?
This book & 6500+ ebooks & video courses on 1000+ technologies
60+ curated reading lists for various learning paths
50+ new titles added every month on new and emerging tech
Early Access to eBooks as they are being written
Personalised content suggestions
Customised display settings for better reading experience
50+ new titles added every month on new and emerging tech
Playlists, Notes and Bookmarks to easily manage your learning
Mobile App with offline access
What do you get with eBook?
Download this book in EPUB and PDF formats
Access this title in our online reader
DRM FREE - Read whenever, wherever and however you want
Online reader with customised display settings for better reading experience
What do you get with video?
Download this video in MP4 format
Access this title in our online reader
DRM FREE - Watch whenever, wherever and however you want
Online reader with customised display settings for better learning experience
What do you get with video?
Stream this video
Access this title in our online reader
DRM FREE - Watch whenever, wherever and however you want
Online reader with customised display settings for better learning experience
What do you get with Audiobook?
Download a zip folder consisting of audio files (in MP3 Format) along with supplementary PDF
What do you get with Exam Trainer?
Flashcards, Mock exams, Exam Tips, Practice Questions
Access these resources with our interactive certification platform
Mobile compatible-Practice whenever, wherever, however you want
BUY NOW $10 p/m for first 3 months. $15.99 p/m after that. Cancel Anytime!
eBook $41.99 $28.99
Print $51.99
Subscription $15.99 $10 p/m for three months
What do you get with a Packt Subscription?
This book & 7000+ ebooks & video courses on 1000+ technologies
60+ curated reading lists for various learning paths
50+ new titles added every month on new and emerging tech
Early Access to eBooks as they are being written
Personalised content suggestions
Customised display settings for better reading experience
50+ new titles added every month on new and emerging tech
Playlists, Notes and Bookmarks to easily manage your learning
Mobile App with offline access
What do you get with a Packt Subscription?
This book & 6500+ ebooks & video courses on 1000+ technologies
60+ curated reading lists for various learning paths
50+ new titles added every month on new and emerging tech
Early Access to eBooks as they are being written
Personalised content suggestions
Customised display settings for better reading experience
50+ new titles added every month on new and emerging tech
Playlists, Notes and Bookmarks to easily manage your learning
Mobile App with offline access
What do you get with eBook + Subscription?
Download this book in EPUB and PDF formats, plus a monthly download credit
This book & 6500+ ebooks & video courses on 1000+ technologies
60+ curated reading lists for various learning paths
50+ new titles added every month on new and emerging tech
Early Access to eBooks as they are being written
Personalised content suggestions
Customised display settings for better reading experience
50+ new titles added every month on new and emerging tech
Playlists, Notes and Bookmarks to easily manage your learning
Mobile App with offline access
What do you get with a Packt Subscription?
This book & 6500+ ebooks & video courses on 1000+ technologies
60+ curated reading lists for various learning paths
50+ new titles added every month on new and emerging tech
Early Access to eBooks as they are being written
Personalised content suggestions
Customised display settings for better reading experience
50+ new titles added every month on new and emerging tech
Playlists, Notes and Bookmarks to easily manage your learning
Mobile App with offline access
What do you get with eBook?
Download this book in EPUB and PDF formats
Access this title in our online reader
DRM FREE - Read whenever, wherever and however you want
Online reader with customised display settings for better reading experience
What do you get with video?
Download this video in MP4 format
Access this title in our online reader
DRM FREE - Watch whenever, wherever and however you want
Online reader with customised display settings for better learning experience
What do you get with video?
Stream this video
Access this title in our online reader
DRM FREE - Watch whenever, wherever and however you want
Online reader with customised display settings for better learning experience
What do you get with Audiobook?
Download a zip folder consisting of audio files (in MP3 Format) along with supplementary PDF
What do you get with Exam Trainer?
Flashcards, Mock exams, Exam Tips, Practice Questions
Access these resources with our interactive certification platform
Mobile compatible-Practice whenever, wherever, however you want
  1. Free Chapter
    Chapter 1: Introducing C# 10.0 and .NET 6
About this book
Writing high-performance code while building an application is crucial, and over the years, Microsoft has focused on delivering various performance-related improvements within the .NET ecosystem. This book will help you understand the aspects involved in designing responsive, resilient, and high-performance applications with the new version of C# and .NET. You will start by understanding the foundation of high-performance code and the latest performance-related improvements in C# 10.0 and .NET 6. Next, you’ll learn how to use tracing and diagnostics to track down performance issues and the cause of memory leaks. The chapters that follow then show you how to enhance the performance of your networked applications and various ways to improve directory tasks, file tasks, and more. Later, you’ll go on to improve data querying performance and write responsive user interfaces. You’ll also discover how you can use cloud providers such as Microsoft Azure to build scalable distributed solutions. Finally, you’ll explore various ways to process code synchronously, asynchronously, and in parallel to reduce the time it takes to process a series of tasks. By the end of this C# programming book, you’ll have the confidence you need to build highly resilient, high-performance applications that meet your customer's demands.
Publication date:
July 2022
Publisher
Packt
Pages
660
ISBN
9781800564718

 

Chapter 1: Introducing C# 10.0 and .NET 6

Microsoft .NET 6 and C# 10.0 are the latest incarnations of the .NET platform and C# programming language. They bring many performance enhancements to the C# and .NET programmer community. We will start this book with an overview of the new versions of C# and .NET.

In this chapter, you will start by downloading, restoring, building, and testing the latest version of the .NET compiler called Roslyn. Then, you will review what's new in .NET 6, including the areas where performance has been greatly enhanced. Then, you will review what's new in C# 10.0 by looking at some code examples that demonstrate these features.

In the Native compilation section, you will build a project and run it as an MSIL project with multiple binaries, then compile and run it as a single native binary. Finally, you will learn how to improve the performance of Windows Store applications and ASP.NET websites.

In this chapter, we will cover the following topics:

  • Overview of .NET 6: In this section, we will cover, at a high level, what's new in .NET 6. You will learn about the various performance improvements that will be part of .NET 6.
  • Overview of C# 10.0: Having learned how to obtain the latest Roslyn code in the Technical requirements section, in this section, you will learn about the various features that will be part of C# 10.0. This will include code examples.
  • Native compilation: In this section, you will learn how to compile a .NET Core application into a single native executable. You will write a simple console application that recursively converts audio files from one format into another.
  • Improving Windows Store performance: This is a brief section that provides standard guidelines for improving the performance of applications that target the Windows Store.
  • Improving ASP.NET performance: This is a brief section that provides some standard guidelines for improving ASP.NET applications.

By the end of this chapter, you will have the following skills:

  • You will understand what's new in Microsoft .NET 6.
  • You will be able to apply the new C# 10.0 code features within your source code.
  • You will be able to compile your source code to native assemblies (also known as binaries).
  • You will know what, how, and where to look for information on improving the performance of applications that target the Windows Store.
  • You will know what, how, and where to look for information on improving the performance of ASP.NET applications.

Let's begin this chapter by looking at Microsoft .NET 6.

 

Technical requirements

You will need the following prerequisites to complete this chapter:

Obtaining and building the latest Roslyn compiler from the source code

Note

The build system of all .NET-related repositories has been in flux for several years now. We will provide the instructions for compiling Roslyn here; these were correct at the time of writing. For the latest instructions, please read the README.md file located at https://github.com/dotnet/roslyn.

The following instructions are for downloading and building the latest version of the Roslyn compiler source on Windows 10:

  1. In the root of the C:\ drive, clone the Roslyn source code by using the following command in the Windows Command Prompt:
    git clone https://github.com/dotnet/roslyn.git
  2. Then, run the following command:
    cd Roslyn
  3. Restore the Roslyn dependencies by running the following command:
    restore.cmd
  4. Build the Roslyn source code by running the following command:
    build.cmd
  5. Test the Roslyn build by running the following command:
    test.cmd
  6. Once all the tests have finished running, check the versions of C# that are accessible to the new computer. Do this by opening a Command Prompt window and navigating to C:\roslyn\artifacts\bin\csc\Debug\net472.
  7. Then, run the following command:
    csc /langversion:?

    Note

    I always run my Command Prompt as an administrator. Hence, the screenshots will show Command Prompt in administrative mode. But running Command Prompt as an administrator is not necessary for this exercise. Where Command Prompt must be executed as an administrator, this will be made clear as needed.

You should see something equivalent to the following:

Figure 1.1 – The versions of the C# programming language supported by the compiler

Figure 1.1 – The versions of the C# programming language supported by the compiler

As you can see, at the time of writing, version 10.0 of the C# language is available via the C# compiler. C# 10.0 is set as the default. The preview is still under development. The default version may be different on your computer.

Note

The latest version of Visual Studio 2022 should allow you to use the latest available C# 10.0 code features. If it doesn't, then compile the latest source and overwrite the files located at C:\Program Files (x86)\Microsoft Visual Studio\2022\Preview\MSBuild\Current\Bin\Roslyn.

The following three sets of instructions provide compiler help for compiling a program that targets a specific C# version and then runs the program. These commands are for demonstrative purposes only, and you do not have to run them now:

csc /help
csc -langversion:10.0 /out:HelloWorld.exe Program.cs
csc HelloWorld

Now that you can build C# 10.0 from the command line and from within Visual Studio 2022, let's learn what kind of new development is taking place with Microsoft .NET 6.

 

Overview of Microsoft .NET 6

Microsoft .NET 6 is the latest incarnation of .NET. You can access the downloads at https://dotnet.microsoft.com/download/dotnet/6.0. The downloads are available for Windows, macOS, and Linux users.

Note

To get the most out of .NET 6 and C# 10.0, it is best that you have Visual Studio 2022 or later installed.

The .NET 6 API documentation is available at https://docs.microsoft.com/dotnet/api/?view=net-6.0.

Microsoft .NET 5 and later will no longer carry the Core or Framework suffix, as per the following article: https://redmondmag.com/articles/2019/12/31/coming-in-2020-net-5.aspx. Microsoft's goal with version 5 and later of the .NET platform is to create a single platform for the .NET development of WinForms, WPF, Xamarin.Forms, ASP.NET Core, and all other forms of .NET development. Xamarin.Forms becomes Microsoft MAUI, with the main difference between versions being that the new Microsoft MAUI will only use a single project to target all operating systems and devices.

Moving to one unified platform

The infrastructure for .NET 6 consists of runtime components, compilers, and languages. Microsoft .NET SDK will sit on top of this infrastructure. The tools that will be available include the command-line interface, Visual Studio Code, Visual Studio for Mac, and, of course, Visual Studio.

With the unified platform, you can write desktop applications using WinForms, WPF, and UWP. Web applications can be written using ASP.NET. Cloud applications will target Microsoft Azure. Mobile applications will be written using Microsoft MAUI. Games, virtual reality (VR), and augmented reality (AR) applications will be developed in Unity, using Visual Studio 2022 or higher as the C# code editor. IoT will target ARM32 and ARM64 architectures. Finally, you will be able to develop artificial intelligence (AI) applications using ML.NET and .NET for Apache Spark.

Microsoft is planning on producing a single .NET runtime and framework that is uniform in its developer experience and runtime behavior across applications and devices. This will be accomplished by building a single code base that combines the best elements of.NET Framework, .NET Core, Mono, and Xamarin.Forms.

The main features of.NET 6 are as follows:

  • Unified developer experiences, regardless of the applications being developed and the devices being targeted.
  • Unified runtime experiences across all devices and platforms.
  • Java interoperability will be available on all platforms. This is stated in the Redmond Magazine article called Coming in 2020: .NET 5, The Next Phase of Microsoft's .NET Framework: https://redmondmag.com/articles/2019/12/31/coming-in-2020-net-5.aspx.
  • Multiple operating systems will be supported for Objective-C and Swift.
  • AOT will be supported by CoreFX to provide static .NET compilation, support multiple operating systems, and produce assemblies that are smaller in size.

Now, let's look at some of the new features of .NET 6 from a high-level viewpoint.

Garbage collection

The garbage collector's performance regarding marking and stealing has been improved. When a thread has finished its marking allotment, it can steal outstanding marking work from other threads. This speeds up the process of collecting items to be garbage collected. Reduced lock contentions on computers with higher core counts, improved de-committing, avoidance of costly memory resets, and vectorized sorting are just some of the new garbage collection performance improvements in .NET 6.

Just-In-Time compiler

In .NET 6, the Just-In-Time (JIT) compiler has also been improved. You can apply various optimizations to the JIT, and it has an unlimited amount of time to implement those optimizations. Ahead-Of-Time (AOT) is just one of the various techniques provided to the JIT so that it can compile as much code as it can before executing the application. The JIT now sees the length of an array as unsigned, which improves the performance of mathematical operations carried out on an array's length. There are still many changes being made.

Suffice to say that between the JIT and the GC, the performance improvements that have been made to JIT and GC concerning memory and compilation optimizations are just two reasons alone to migrate to .NET 6.

The JIT also recognizes more than a thousand new hardware intrinsic methods. These methods allow you to target various hardware instruction sets from C#. You are no longer tied to just x86_x64 hardware instruction sets.

Several runtime helper functions are available in the JIT. These helper functions enable the JIT compiler to manipulate the source code so that the code runs must faster. Generic lookups are much faster now, as they no longer need to employ slower lookup tables.

Text-based processing

Performance enhancements have also been made within the text-based processing elements of .NET 6. These include (but are not limited to) processing whitespace in the System.Char class, which requires less branching and fewer arguments. Because this class is used in various text-processing objects and methods within .NET 6, the speed of processing text in .NET 6 will be generally improved. DateTime processing is also at least 30% faster due to optimizations in extracting the date and time components from the raw tick count. Performance improvements have also been made to string operations due to culture-aware modifications of StartsWith and EndsWith. By utilizing stack allocation and JIT devirtualization, the performance of data encoding, such as UTF8 and Latin1 encoding, has also been enhanced.

Regular expression (RegEx) performance has also been improved in .NET 6. The RegEx engine has had performance improvements that increase textual processing by up to three to six times and even more. The CharInClass method is more intelligent in determining if characters appear within the specified character class. Character and digit comparisons use lookup tables and various method calls are inlined, providing improved RegEx processing. Generated code for various expressions has been improved. Searching for RegExes is carried out using span-based searching with vectorized methods. The need for backtracking has been eliminated as it analyzes RegExes during the node tree optimization phase and adds atomic groups that do not change the semantics but do prevent backtracking. These are only some of the improvements to RegEx performance. But there are many more.

Note

For more in-depth knowledge on .NET 5 performance improvements to RegExes, please read the following very detailed post by Stephen Toub: https://devblogs.microsoft.com/dotnet/regex-performance-improvements-in-net-5/.

Threading and asynchronous operations

Threading and asynchronous operations have also received a performance boost in .NET 5 with the experimental addition of async ValueTask pooling. You can turn on pooling by setting DOTNET_SYSTEM_THREADING_POOLASYNCVALUETASK to true or 1. Pooling creates state machine box objects that implement the interfaces, IvalueTaskSource, and IValueTaskSource<TResult>. The runtime adds these objects to the pool. Volatility has also received performance improvements in ConcurrentDictionary, with performance improving as much as 30% on some ARM architectures.

Collections and LINQ

The collections have also seen several performance enhancements, mainly to Dictionary<TKey, TValue>, HashSet<T>, ConcurrentDictionary<TKey, TValue>, and System.Collections.Immutable. The HashSet<T> collection's implementation has been rewritten and re-synchronized with Dictionary<TKey, the TValue> implementation, and moved further down the stack. The performance of foreach when iterating through an ImmutableArray<T> has been improved, and the generated code has been reduced in size by the addition of the [MethodImpl(MethodImplOptions.AggressiveInlining)] annotation to the GetEnumerator method of ImmutableArray<T>. Other elements of the .NET collections, such as BitArray, have also seen performance improvements.

In .NET 5, LINQ has also seen further performance improvements, including OrderBy, Comparison<T>, Enumerable.SkipLast, and by making implementing Enumerable.Any more consistent with Enumerable.Count. These are only a few performance improvements that have been to the collections.

Networking and Blazor

Networking has received a lot of work on performance improvement, especially the System.Uri class (especially in its construction). The System.Net.Sockets and System.Net.Http namespaces have also seen performance improvements. Many improvements have been made to how JSON is processed with JsonSerializer in the System.Text.Json library for .NET.

As Blazor uses the .NET mono runtime and .NET 5 libraries, a linker has been added that trims code from the assembly that is not used down to the member level. The code to be trimmed is identified by static code analysis. User interface response times are also improved in Blazor Web Assembly applications, as the client-side code is downloaded before being executed, and behaves just like a desktop application – but from within the browser.

Furthermore, general improvements that have gone into .NET 5 include faster assembly loading, faster mathematical operations, faster encryption and decryption, faster interoperability, faster reflection emitting, faster I/O, and various allocations in various libraries.

New performance-based APIs and analyzers

A few new performance-focused APIs have been added to .NET 5. Internally, some of these APIs are already being used to reduce code size and improve the performance of .NET 5 itself. They focus on helping the programmer to concentrate on writing performant code and removing the complexity of tasks that have been previously hard to accomplish. These new APIs and improvements to existing APIs include Decimal, GC, MemoryExtensions, StringSplitOptions, BinaryPrimitives, MailAddress, MemoryMarshall, SslStream, HttpClient, and more.

The .NET 5 SDK has also seen the addition of some new performance-based analyzers. These analyzers can detect accidental allocations as a part of range indexing and offer ways to eliminate the allocation. Analyzers will detect the old overloads for the Stream.Read/WriteAsync methods and will offer fixes to enable automatic switching to the newer overload methods that prefer Memory overloads. In StringBuilder, it is more performant to use typed overloads to append non-string values such as int and long values. When situations are encountered by the analyzer where the programmer has called ToString() on a type that's being appended for which a typed overload exists, the fixer will detect these situations and automatically switch to using the correct typed overload. With LINQ, it is now more efficient to check if (collection.Count != 0) using the (!collection.IsEmpty) syntax. The old way will be detected by the analyzer and fixed to use the more performant new way. Finally, when you have worked to make your code faster, your code is made correct, as the analyzer flags cases that use loops to allocate memory from the stack using stackalloc. This helps prevent stack overflow exceptions from being raised.

To see the road ahead in terms of .NET's new development, you can view the .NET Core roadmap located at https://github.com/dotnet/core/blob/master/roadmap.md.

Now, let's look at C# 10.0.

 

Overview of C# 10.0

You can find the features that will become part of C# 10.0 on the Roslyn GitHub page at https://github.com/dotnet/roslyn/blob/master/docs/Language%20Feature%20Status.md.

Not all these features are available at the time of writing. However, we will look at some of the available features. With that, let's start with top-level programs.

Writing top-level programs

Before C# 9.0, the Hello, World! console application was always the starting point for learning C#. The file that students would update was called Program.cs. In this file, you would have something akin to the following:

using System;
namespace HelloWorld
{
class Program
{
            static void Main(string[] args)
            {
                  Console.WriteLine("Hello, World!");
            }
}
}

As you can see, first, we import our System library. Then, we have a namespace definition followed by our class definition. Then, in the class definition, we have our Main method, in which we output the phrase "Hello, World!" to the console window.

In version 10.0 of the C# programming language, this can be simplified down to a single line:

System.Console.WriteLine("Hello, World");

Here, we have eradicated 10 lines of code. Running the program will output the following:

Figure 1.2 – The console window showing the output "Hello World!"

Figure 1.2 – The console window showing the output "Hello World!"

If we open the generated DLL in IL DASM, we will see the following:

Figure 1.3 – ILDASM showing the internals of the hello world program

Figure 1.3 – ILDASM showing the internals of the hello world program

You will see from the decompilation that the compiler adds the Main method at compile time. The next addition to C# 10.0 that we will look at is init-only properties.

Using init-only properties

Init-only properties allow you to use object initializers with immutable fields. For our little demonstration, we will use a Book class that holds the name of a book and its author:

namespace CH01_Books
{
    internal class Book
    {
        public string Title { get; init; }
        public string Author { get; init; }
    }
}

The properties can be initialized when the book is created. But once created, they can only be read, not updated, making the Book type immutable. Now, let's look at init-only properties. In the Program class, replace its contents with the following:

using System;
using CH01_Books;
var bookName = new Book { Title = "Made up book name", 
    Author = "Made Up Author" };
Console.WriteLine($"{bookName.Title} is written by 
    {bookName.Author}. Well worth reading!");

Here, we imported the System and CH01_Books namespaces. Then, we declared a new immutable variable of the Book type. After that, we output the contents of that Book type using an interpolated string. Run the program; you should see the following output:

Figure 1.4 – The output of our init-only properties example

Figure 1.4 – The output of our init-only properties example

Now that we have been introduced to init-only properties, let's look at records.

Using records

When updating data, you do not want that data to be changed by another thread. So, in multi-threaded applications, you will want to use thread-safe objects when making updates. Records allow complete objects to be immutable and behave as values. The advantage of using records over structs is that they require less memory to be allocated to them. This reduction in memory allocation is accomplished by compiling records to reference types. They are then accessed via references and not as copies. Due to this, other than the original record allocation, no further memory allocation is required.

Let's learn how to use records. Start a new console application.

To demonstrate the use of records, we will use the following Book example:

internal record Book
{
public string Title { get; init; }
     public string Author { get; init; }
}

The only change to the Book class is that class has been replaced with record. Everything else remains the same. Now, let's put the record to work:

  1. Replace the contents of the Program class with the following code:
    using System;
    using CH01_Records;
    var bookOne = new Book { 
        Title = "Made Up Book", 
        Author = "Made Up Author
    };
    var bookTwo = bookOne with { 
        Title = "And Another Made Up Book"
    };
    var bookThree = bookTwo with { 
        Title = "Yet Another Made Up Book"
    };
    var bookFour = bookThree with { 
        Title = "And Yet Another Made Up Book: Part 1",
    };
    var bookFive = bookFour with { 
        Title = "And Yet Another Made Up Book: Part 2"
    };
    var bookSix = bookFive with { 
        Title = "And Yet Another Made Up Book: Part 3"
    };
    Console.WriteLine($"Some of {bookThree.Author}'s 
        books include:\n");
    Console.WriteLine($"- {bookOne.Title}");
    Console.WriteLine($"- {bookTwo.Title}");
    Console.WriteLine($"- {bookThree.Title}");
    Console.WriteLine($"- {bookFour.Title}");
    Console.WriteLine($"- {bookFive.Title}");
    Console.WriteLine($"- {bookSix.Title}");
    Console.WriteLine($"\nMy favourite book by {bookOne.
        Author} is {bookOne.Title}.");
  2. As you can see, we are creating immutable record types. We can create new immutable types from them and change any fields we like using the with expression. The original record is not mutated in any way. Run the code; you will see the following output:

Figure 1.5 – Init-only properties showing their immutability

Figure 1.5 – Init-only properties showing their immutability

Despite changing the title during the assignment, the original record has not been mutated at all.

  1. Records can also use inheritance. Let's add a new record that contains the publisher's name:
        internal record Publisher
        {
            public string PublisherName { get; init; }
        }
  2. Now, let's have our Book inherit this Publisher record:
        internal record Book : Publisher
        {
            public string Title { get; init; }
            public string Author { get; init; }
        }
  3. Book will now include PublisherName. When we initialize a new book, we can now set its PublisherName:
    var bookOne = new Book { 
        Title = "Made Up Book", 
        Author = "Made Up Author",
        PublisherName = "Made Up Publisher Ltd."
    };
  4. Here, we have created a new Book that contains Publisher.PublisherName. Let's print the publisher's name. Add the following line to the end of the Program class:
    Console.WriteLine($"These books were originally published 
        by {bookSix.PublisherName}.");
  5. Run the code; you should see the following output:

Figure 1.6 – Init-only properties using inheritance

Figure 1.6 – Init-only properties using inheritance

  1. As you can see, we never set the publisher's name for bookTwo to bookSix. However, the inheritance has followed through from when we set it for bookOne.
  2. Now, let's perform object equality checking. Add the following code to the end of the Program class:
    var book = bookThree with { Title = "Made Up Book" };
    var booksEqual = Object.Equals(book, bookOne) ? 
        "Yes" : "No";
    Console.WriteLine($"Are {book.Title} and 
        {bookOne.Title} equal? {booksEqual}"); 
  3. Here, we created a new Book from bookThree and set the title to Made Up Book. Then, we performed an equality check and output the result to the console window. Run the code; you will see the following output:

Figure 1.7 – Init-only properties showing the result of an equality check

Figure 1.7 – Init-only properties showing the result of an equality check

It is clear to see that the equality check works with both book instances being equal.

  1. Our final look at records considers positional records. Positional records set data via the constructor and extract data via the deconstructor. The best way to understand this is with code. Add a class called Product and replace the class with the following:
        public record Product
        {
            readonly string Name;
            readonly string Description;
            public Product(string name, string 
                description) 
                => (Name, Description) = (name, 
                description);
            public void Deconstruct(out string name, out 
                string description) 
                => (name, description) = (Name, 
                    Description);
        }
  2. Here, we have an immutable record. The record has two private and readonly fields. They are set in the constructor. The Deconstruct method is used to return the data. Add the following code to the Program class:
    var ide = new Product("Awesome-X", "Advanced Multi-
        Language IDE");
    var (product, description) = ide;
    Console.WriteLine($"The product called {product} is an 
        {description}.");

In this code, we created a new product with parameters for the name and description. Then, we declared two fields called product and description. The fields are set by assigning the product. Then, we output the product and description to the console window, as shown here:

Figure 1.8 – Init-only positional records

Figure 1.8 – Init-only positional records

Now that we have finished looking at records, let's look at the improved pattern matching capabilities of C# 10.0.

Using the new pattern matching features

Now, let's look at what's new for pattern matching in C# 10.0, starting with simple patterns. With simple pattern matching, you no longer need the discard (_) operator to just declare the type. In our example, we will apply discounts to orders:

  1. Add a new record called Product to a new file called Product.cs in a new console application and add the following code:
        internal record Product
        {
            public string Name { get; init; }
            public string Description { get; init; }
            public decimal UnitPrice { get; init; }
        }
  2. Our Product record has three init-only properties for Name, Description, and UnitPrice. Now, add the OrderItem record that inherits from Product:
        internal record OrderItem : Product
        {
            public int QuantityOrdered { get; init; }
        }
  3. Our OrderItem record inherits the Product record and adds the QuantityOrdered init-only property. In the Program class, we will add three variables of the OrderItem type and initialize them. Here is the first OrderItem:
    var orderOne = new OrderItem {
            Name = "50-80mm Scottish Cobbles", 
            Description = "These rounded stones are 
              frequently used for edging paths and to add 
                interest to gardens", 
            QuantityOrdered = 4, 
            UnitPrice = 199 
    };

As you can see, the quantity that's being ordered is 4.

  1. Add orderTwo with the same values but with an OrderQuantity of 7.
  2. Then, add orderThree with the same values, but with an OrderQuantity of 31. We will demonstrate simple pattern matching in the GetDiscount method:
    static int GetDiscount(object order) =>
        order switch
        {
            OrderItem o when o.QuantityOrdered == 0 => 
                throw 
              new ArgumentException("Quantity must be 
                  greater than zero."),
            OrderItem o when o.QuantityOrdered > 20 => 30,
            OrderItem o when o.QuantityOrdered < 5 => 10,
            OrderItem => 20,
            _ => throw new ArgumentException("Not a known 
                OrderItem!", nameof(order))
        };
  3. Our GetDiscount method receives an order. QuantityOrdered is then evaluated. Argument exceptions are thrown if the order quantity is 0 and if the object type that's been passed in is not of the OrderItem type. Otherwise, a discount of the int type is returned for the quantity ordered. Notice that we use the type without using the discard operator on the line for the 20% discount.
  4. Finally, we must add the following lines to the end of the Program class:
    Console.WriteLine($"The discount for Order One is 
        {GetDiscount(orderOne)}%.");
    Console.WriteLine($"The discount for Order Two is 
        {GetDiscount(orderTwo)}%.");
    Console.WriteLine($"The discount for Order Three is 
        {GetDiscount(orderThree)}%.");
  5. These lines print the discount received for each of the orders to the console window. Now, let's modify our code so that it uses relational pattern matching. Add the following method to the Program class:
    static int GetDiscountRelational(OrderItem orderItem) 
        => orderItem.QuantityOrdered switch
        {
            < 1 => throw new ArgumentException("Quantity 
                must be greater than zero."),
            > 20 => 30,
            < 5 => 10,
            _ => 20
        };
  6. Using relational pattern matching, we have received the same outcome as with simple pattern matching, but with less code. It is also very readable, which makes it easy to maintain. Add the following three lines of code to the end of the Program class:
    Console.WriteLine($"The discount for Order One is 
        {GetDiscountRelational(orderOne)}%.");
    Console.WriteLine($"The discount for Order Two is 
        {GetDiscountRelational(orderTwo)}%.");
    Console.WriteLine($"The discount for Order Three is 
        {GetDiscountRelational(orderThree)}%.");
  7. In these three lines, we simply output the discount for each order to the console window. Run the program; you will see the following output:
Figure 1.9 – Simple and relational pattern matching output showing the same results

Figure 1.9 – Simple and relational pattern matching output showing the same results

From the preceding screenshot, you can see that the same outcome has been received for both discount methods.

  1. The logical AND, OR, and NOT methods can be used in logical pattern matching. Let's add the following method:
    static int GetDiscountLogical(OrderItem orderItem) =>
        orderItem.QuantityOrdered switch
        {
            < 1 => throw new ArgumentException("Quantity 
                must be greater than zero."),
            > 0 and < 5 => 10,
            > 4 and < 21 => 20,
            > 20 => 30
        };
  2. In the GetDiscountLogical method, we employ the logical AND operator to check whether a value falls in that range. Add the following three lines to the end of the Program class:
    Console.WriteLine($"The discount for Order One is 
        {GetDiscountLogical(orderOne)}%.");
    Console.WriteLine($"The discount for Order Two is 
        {GetDiscountLogical(orderTwo)}%.");
    Console.WriteLine($"The discount for Order Three is 
        {GetDiscountLogical(orderThree)}%.");
  3. In those three lines of code, we output the discount value for the order to the console window. Run the code; you will see the following output:
Figure 1.10 – Simple, relational, and logical pattern matching showing the same results

Figure 1.10 – Simple, relational, and logical pattern matching showing the same results

The output for the logical pattern matching is the same as for simple and relational pattern matching. Now, let's learn how to use new expressions with targeted types.

Using new expressions with targeted types

You can omit the type of object being instantiated. But to do so, the declared type must be explicit and not use the var keyword. If you attempt to do this with the ternary operator, you will be greeted with an exception:

  1. Create a new console application and add the Student record:
        public record Student
        {
            private readonly string _firstName;
            private readonly string _lastName;
            public Student(string firstName, string 
                lastName)
            {
                _firstName = firstName;
                _lastName = lastName;
            }
            public void Deconstruct(out string firstName, 
                out string lastName)
                => (firstName, lastName) = (_firstName, 
                    _lastName);
        }
  2. Our Student record stores the first and last name values, which have been set via the constructor. These values are obtained via the out parameters of the Deconstruct method. Add the following code to the Program class:
    Student jenniferAlbright = new ("Jennifer", 
        "Albright");
    var studentList = new List<Student>
    {
        new ("Jennifer", "Albright"),
        new ("Kelly", "Charmichael"),
        new ("Lydia", "Braithwait")
    };
    var (firstName, lastName) = jenniferAlbright;
    Console.WriteLine($"Student: {lastName}, {firstName}");
    (firstName, lastName) = studentList.Last();
    Console.WriteLine($"Student: {lastName}, {firstName}");
  3. First, we instantiate a new Student without declaring the type in the new statement. Then, we instantiate a new List and add new students to the list while omitting the Student type. The fields are then defined for firstName and lastName and assigned their values through the assignment of the named student. The student's name is then printed out on the console window. Next, we take those fields and reassign them with the name of the last student on the list. Then, we output the student's name to the console window. Run the program; you will see the following output:
Figure 1.11 – Using targeted types with new expressions

Figure 1.11 – Using targeted types with new expressions

From the preceding screenshot, you can see that we have the correct student names printed. Now, let's look at covariant returns.

Using covariant returns

With covariant returns, base class methods with less specific return types can be overridden with methods that return more specific types. Have a look at the following array declaration:

object[] covariantArray = new string[] { "alpha", "beta", 
    "gamma", "delta" };

Here, we declared an object array. Then, we assigned a string array to it. This is an example of covariance. The object array is the least specific array type, while the string array is the more specific array type.

In this example, we will instantiate covariant types and pass them into a method that accepts less and more specific types. Add the following class and interface declarations to the Program class:

public interface ICovariant<out T> { }
public class Covariant<T> : ICovariant<T> { }
public class Person { }
public class Teacher : Person { }
public class Student : Person { }

Here, we have a covariant class that implements a covariant interface. We declared a general type of Person that is inherited by the specific Teacher and Student types. Add CovarianceClass, as shown here:

public class CovarianceExample
{
public void CovariantMethod(ICovariant<Person> person)
{ 
      Console.WriteLine($"The type of person passed in is 
          of type {person.GetType()}.");
}
}

In the CovarianceExample class, we have a CovariantMethod with a parameter that can accept objects of the ICovariant<Person> type. Now, let's put covariance to work by adding the CovarianceAtWork method to the CovarianceExample class:

public void CovarianceAtWork()
{
ICovariant<Person> person = new Covariant<Person>();
ICovariant<Teacher> teacher = new Covariant<Teacher>();
ICovariant<Student> student = new Covariant<Student>();
CovariantMethod(person);
CovariantMethod(teacher);
CovariantMethod(student);
}

In this method, we have the general Person type and the more specific Teacher and Student types. We must pass each into CovariantMethod. This method can take the less specific Person type and the more specific Teacher and Student types.

To run the CovarianceAtWork method, place the following code after the using statement and before the covariantArray example:

CovarianceExample.CovarianceAtWork();

Now, let's look at native compilation.

 

Native compilation

When .NET code is compiled, it is compiled into Microsoft Intermediate Language (MSIL). MSIL gets interpreted by a JIT compiler when it is needed. The JIT compiler then compiles the necessary MSIL code into native binary code. Subsequent calls to the same code call the binary version of the code, not the MSIL version of the code. This means that MSIL code is always slower than native code, as it is compiled to native on the first run.

JIT code has the advantage of being cross-platform code at the expense of longer startup times. The code of an MSIL assembly that runs is compiled to native code by the JIT compiler. The native code is optimized by the JIT compiler for the target hardware it is running on.

By default, UWP applications are compiled to native code using .NET Native, while iOS applications are compiled to native code via Xamarin/Xamarin.Forms. Microsoft .NET Core can also be compiled into native code.

Performing native compilation of .NET Core applications

When using dotnet to compile an assembly to native code, you will need to specify a target framework. For a list of supported target frameworks, please refer to https://docs.microsoft.com/en-us/dotnet/standard/frameworks. You will also need to specify a Runtime Identifier (RID). For a list of supported RIDs, please refer to https://docs.microsoft.com/en-us/dotnet/core/rid-catalog.

Note

At the time of writing, native compilation against .NET 5.0 does have its issues. So, to keep things simple, we will demonstrate native compilation into a single executable against netcoreapp3.1 and win10-x64.

To demonstrate the compilation of Microsoft .NET Core applications into natively compiled single executables, we will write a simple demonstration application that traverses a directory structure and converts audio files from one format into another:

  1. Start a new console application and target .NET 6.
  2. Visit https://ffmpeg.org/download.html and download ffmpeg for your operating system. Mine is Windows 10.
  3. On Windows 10, extract the ffmpeg files into the C:\Tools\ffmpeg folder. Add the following using statements to the top of the Program.cs file:
    using System;
    using System.Diagnostics;
    using System.IO;
  4. We will be batch processing audio files in a folder hierarchy on our local systems. Here, the using statements listed will help us debug our code and perform I/O on the filesystem. Now, at the top of the Program class, add the following three fields:
    private static string _baseDirectory = string.Empty;
    private static string _sourceExtension = string.Empty;
    private static string _destinationExtension = string
        .Empty;
  5. The BaseDirectory member holds the starting directory that will be processed. sourceExtension holds the extension of the file type, such as .wav, we are after converting to, while destinationExtension holds the extension, such as .ogg, of the file type we are after converting to. Update your Main method so that it looks as follows:
    static void Main(string[] args)
    {
    Console.Write("Enter Source Directory: ");
    _baseDirectory = Console.ReadLine();
    Console.Write("Enter Source Extension: ");
    _sourceExtension = Console.ReadLine();
    Console.Write("Enter Destination Extension: ");
    _destinationExtension = Console.ReadLine();
    new Program().BatchConvert();
    }
  6. In our Main method, we have requested that the user enters the source directory, source extension, and destination extension. Then, we set out member variables and called the BatchConvert method. Let's add our BatchConvert method:
    private void BatchConvert()
    {
    var directory = new DirectoryInfo(_baseDirectory);
    ProcessFolder(directory);
    }
  7. The BatchConvert method creates a new DirectoryInfo object called directory and then passes the directory object into the ProcessFolder method. Let's add this method now:
    private void ProcessFolder(DirectoryInfo 
        directoryInfo)
    {
    Console.WriteLine($"Processing Directory: 
        {directoryInfo.FullName}");
    var fileInfos = directoryInfo.EnumerateFiles();
    var directorieInfos = directoryInfo.
        EnumerateDirectories();
           foreach (var fileInfo in fileInfos)
                 if (fileInfo.Extension.Replace(".", "") 
                     == sourceExtension)
                        ConvertFile(fileInfo);
    foreach (var dirInfo in directorieInfos)
                 ProcessFolder(dirInfo);
    }
  8. The ProcessFolder method outputs a message to the screen so that the user knows what folder is being processed. Then, it obtains an enumeration of the FileInfo and DirectoryInfo objects from the directoryInfo parameter. After this, it converts all the files in that folder that have the required source file extension. Once all the files have been processed, each of the DirectoryInfo objects is processed by calling the ProcessFolder method recursively. Finally, let's add our ConvertFile method:
    private void ConvertFile(FileInfo fileInfo)
    {    
    }
  9. Our ConvertFile method takes a FileInfo parameter. This parameter contains the file that is to undergo conversion. The remaining code will be added to this ConvertFile method. Add the following three variables:
    var timeout = 10000;
    var source = $"\"{fileInfo.FullName}\"";
    var destination = $"\"{fileInfo.FullName.Replace
         (_sourceExtension, _destinationExtension)}\"";
  10. The timeout variable is set to 10 seconds. This gives the process 10 seconds to process each file. The source variable contains the full name of the file to be converted, while the destination variable contains the full path of the newly converted file. Now, add the check to see if the converted file exists:
    if (File.Exists(fileInfo.FullName.Replace
         (_sourceExtension, _destinationExtension)))
    {
    Console.WriteLine($"Unprocessed: {fileInfo.FullName}");
           return;
    }
  11. If the destination file exists, then the conversion has already taken place, so we do not need to process the file. So, let's output a message to the user to inform them that the file is unprocessed, and then return from the method. Let's add the code to perform the conversion:
    Console.WriteLine($"Converting file: {fileInfo.FullName}
        from {_sourceExtension} to {_destination
            Extension}.");
    using var ffmpeg = new Process
    {
    StartInfo = {
                 FileName = @"C:\Tools\ffmpeg\bin
                    \ffmpeg.exe",
                  Arguments = $"-i {source} 
                    {destination}",
                  UseShellExecute = false,
                  RedirectStandardOutput = true,
                  RedirectStandardError = true,
                  CreateNoWindow = true
    }
    };
    ffmpeg.EnableRaisingEvents = false;
    ffmpeg.OutputDataReceived += (s, e) => Debug.WriteLine
         ($"Debug: e.Data");
    ffmpeg.ErrorDataReceived += (s, e) => Debug.WriteLine
         ($@"Error: {e.Data}");
    ffmpeg.Start();
    ffmpeg.BeginOutputReadLine();
    ffmpeg.BeginErrorReadLine();
    ffmpeg.WaitForExit(timeout);
  12. Here, we output a message to the window informing the user of the file being processed. Then, we instantiate a new process that executes ffmpeg.exe and converts an audio file from one format into another, as specified by the user. The converted file is then saved in the same directory as the original file.
  13. With that, we have completed our sample project. So, let's see it running. On an external hard disk, I have some Ghosthack audio samples that I own. The files are in .wav file format. However, they need to be transformed into .ogg files to be used in an Android program that I use. You can use your own audio file or music folders.

    Note

    If you don't have any audio files to hand to test this small program, you can download some royalty-free sounds from https://www.bensound.com. You can check the following page for links to various public music domains: https://www.lifewire.com/public-domain-music-3482603.

  14. Fill out the questions and press Enter:
Figure 1.12 – Our file converter showing the directory and file conversion formats

Figure 1.12 – Our file converter showing the directory and file conversion formats

The program will now process all files and folders under the specified parent folder and process them.

The program is working as expected in its MSIL form. However, we can see the delay in performing the file conversions. Let's compile our file converter into a single native executable, and then see if it is visibly any faster:

  1. Open the Visual Studio Developer Command Prompt as an administrator and navigate to the folder that contains your solution and project file. When publishing the file, it is worth noting that the TargetFramework property of the project should also be updated to netcoreapp3.1; otherwise, this may not work – that is, if it is set to net5.0. Type the following command and then press Enter:
    dotnet publish --framework netcoreapp3.1 -
        p:PublishSingleFile=true --runtime win10-x64
  2. When the command has finished running, your command window should look as follows:
Figure 1.13 – The Developer Command Prompt in administrative mode showing the native compilation output

Figure 1.13 – The Developer Command Prompt in administrative mode showing the native compilation output

  1. If you navigate to the publish directory, you will see the following output:
Figure 1.14 – Windows Explorer displaying the output files resulting from native compilation

Figure 1.14 – Windows Explorer displaying the output files resulting from native compilation

  1. Run the CH01_NativeCompilation.exe file. You will see that .wav files are processed into .ogg files much quicker.

In this section, we learned how to write a console app. We compile the console app to MSIL and then compile the console app into a single native executable file. Visually, from the user's perspective, the file processes batch audio files much quicker in native form than in MSIL form.

Now, let's learn how to improve Windows Store applications.

 

Improving Windows Store performance

Here are some basic tips for improving the performance of Windows Store applications:

In the next section, we'll learn how to improve performance with ASP.NET.

 

Improving ASP.NET performance

Here are some basic tips for improving the performance of web applications and APIs:

  • Perform baseline measurements: Before making changes to the performance of your web application or API, take a baseline reading of your program's performance. This way, you can measure any adjustments to see if they improve performance or slow things down.
  • Begin by optimizing the code with the largest impact: When you have completed your baseline measurements, start performance tuning on the piece of code that is the least performant and that has the biggest impact on your program's performance. This will provide you with your biggest win.
  • Enable HTTP compression: To reduce the size of transmitted files over HTTP/HTTPS and improve network performance, enable compression. There are two types of compression. GZIP compression has been around for many years and is the de facto compression mechanism; it can reduce a file's size by one-third. An alternative compression mechanism is Brotli. Most major browsers have had support for this compression mechanism since 2016/2017.
  • Reduce TCP/IP connection overheads: Reducing HTTP requests seriously improves HTTP communication performance. Each request uses network and hardware resources. When a hardware and software-specific number of connections is established, performance will start to show signs of degrading. This can be mitigated by reducing the number of HTTP requests.
  • Use HTTP/2 over SSL: HTTP/2 over SSL provides various performance improvements of using HTTP. Multiplexed streams provide bi-directional sequences of text format frames. Server push enables a server to push cacheable data to the client in anticipation that the client may use it. Binary protocols have a lower overhead when it comes to parsing data and they are less prone to errors. Binary protocols offer more security and have better network utilization There are many more optimizations that you gain when you switch to HTTP/2 over SSL.
  • Employ minification: Minification is the process of eliminating whitespace and comments in an HTML, CSS, or JavaScript web file. By making the size of the file smaller and by enabling compression, you can seriously speed up the network transmission of files, especially over poor Wi-Fi.
  • Place CSS in the head so that it loads first: To efficiently render a web page, it is best to load the complete CSS before rendering to prevent reflows.
  • Place JavaScript at the end of HTML files: For vanilla HTML, CSS, and JavaScript applications, the preferred location for JavaScript files is at the bottom of HTML files, before the closing body tag. For heavy framework-based applications, bootstrapping will be beneficial as only the JavaScript that is needed is loaded. An alternative is isomorphic JavaScript for rendering pages on both the client and the server. Isomorphic applications improve SEO, performance, and maintainability.
  • Reduce image size: Images can vary greatly in size. Reduce the size of the images that are used on a page. When used with minification and compression, this technique can help fancy-looking web pages load fast.

You can find out more about other techniques for improving ASP.NET performance in the Further reading section. Now, let's summarize what we have learned in this chapter.

 

Summary

At the start of this chapter, you downloaded the latest source for the C# programming language. Then, you restored it, built it, and ran various tests. After that, you built a Hello, World! program that demonstrated C# 9.0 features.

Then, you learned what's new in .NET 5. This section covered topics on garbage collection, JIT compilation, text-based processing, threading and asynchronous operations, collections, LINQ, networking, and Blazor. We also covered the new performance-based APIs and analyzers. From what was covered, you now have a high-level appreciation of the many performance improvements made by Microsoft and third parties to the new version of the .NET programming language. These performance improvements are a solid reason to move to .NET 5. But another compelling reason is also the move to .NET for true cross-platform development from a single code base.

After reviewing the performance improvements and additions to .NET 5, we looked at the new C#10.0 features. You learned how to write a program with just one line of code using top-level statements. Then, you learned how to implement init-only properties, records, new pattern-matching features, new expressions with targeted types, and covariant returns. From reviewing the new additions to the C# 9.0 language, you learned how to compile and run code in MSIL, and then compile and run native code in a single executable file. Visually, the end user experience was shown to be better when using the native binary over the MSIL assembly. For the example, we used a simple audio file format converter.

You were then provided with some guidance on how to improve Windows Store app performance. Links to the official Microsoft documentation were presented to you to help you generate performance reports, along with how to understand the results of the performance assessment. This guidance also highlighted the main metrics to pay attention to. Finally, we considered some ways in which you can improve the performance of your ASP.NET websites and APIs. In the Further reading section, you will find a link to the official Microsoft ASP.NET documentation. This documentation will help you architect and build quality websites.

Furthermore, in the Further reading section, you will find some links to documentation and the GitHub repository for .NET MAUI, which is due to be released in 2021 in concert with .NET 6. This user interface technology is an evolution of Xamarin.Forms with evolutionary changes based on customer research. It does look rather promising.

In the next chapter, we will be looking at .NET interoperability. But before that, work through this chapter's questions to see how well everything has sunk in.

 

Questions and exercises

Answer the following questions regarding this chapter:

  1. What areas of .NET are being improved by .NET 6?
  2. What is new to C# 10.0?
  3. What tools are available for native compilation in .NET?
  4. How can you improve the Windows Store app's performance?
  5. How can you speed up ASP.NET?
  6. Investigate the state of .NET MAUI, the future of frontend desktop and mobile development that is still undergoing development.
  7. Write some console applications and practice using the new features of .NET 6 and C# 10.0.
  8. Use Benchmark.NET to benchmark one of your small applications, and then upgrade it to use .NET 6 and C# 10.0. Measure its performance without making any changes if possible, and then measure its performance again. See if you notice any performance improvements by simply upgrading to C# 10.0 and .NET 6.

    Note

    The answers to questions 4 and 5 can be found in the external reference sources provided in their respective sections.

 

Further reading

To learn more about the topics that were covered in this chapter, take a look at the following resources:

About the Author
  • Jason Alls

    Jason Alls is the author of Clean Code in C# and has been programming for over 21 years. Working with an Australasian company, he started his career developing call center management reporting software used by global clients including telecom providers, banks, airlines, and the police. He then moved on to develop GIS marketing applications and worked in the banking sector performing data migrations between Oracle and SQL Server. Certified as an MCAD in C# since 2005, he has been involved in the development of various desktop, web, and mobile applications. Currently employed by a leading software house, he develops and supports order processing and warehouse management software written in C#.

    Browse publications by this author
Latest Reviews (1 reviews total)
A great book. Good information, well written, relevant and up-to-date. If you think you *might* like this book, you will. What a wonderful world we live in. If only I knew C# better. I will reference this book continuously as I learn more about C# programming and build increasingly complex tools.
High-Performance Programming in C# and .NET
Unlock this book and the full library FREE for 7 days
Start now