Category Archives: C#

Mocking the File system

One problem with files is that testing can be problematic, what file to use? what path to use? how to test when a file is missing? do I really need another file in my Version Control System? how to simulate 100s of files in one directory?

Sometimes it can be useful to Mock these files, for this you can create an interface IFileSystemProvider, which gives all the classic functions of the System.IO.File class and a FileSystemProvider class, which link all these methods to the actual System.IO.File class. If you do not want to instantiate a class for FileSystemProvider you can also create a static class FileSystem, this class instantiate a File variable as a FileSystemProvider by default and offers also all the methods of IFileSystemProvider. If you do this you can use FileSystem.File as if it was System.IO.File, but also mock its behaviour in your unit tests!

The FileSystem class

using System.Collections.Generic;
using System.IO;

namespace FileMocking
{
    public interface IFileSystemProvider
    {
        bool Exists(string path);
        IEnumerable<string> ReadLines(string path);
    }

    public class FileSystemProvider : IFileSystemProvider
    {
        public bool Exists(string path) => File.Exists(path);
        public IEnumerable<string> ReadLines(string path) => File.ReadLines(path);
    }

    public interface IDirectorySystemProvider
    {
        IEnumerable<string> EnumerateFiles(string path);
        bool Exists(string path);
    }

    public class DirectorySystemProvider : IDirectorySystemProvider
    {
        public IEnumerable<string> EnumerateFiles(string path) => Directory.EnumerateFiles(path);
        public bool Exists(string path) => Directory.Exists(path);
    }

    public static class FileSystem
    {
        public static IFileSystemProvider File = new FileSystemProvider();
        public static IDirectorySystemProvider Directory = new DirectorySystemProvider();
    }
}

Using FileSystem in your application

Since FileSystem is a static class, its usage is very similar to System.IO.File and System.IO.Directory. Here are only a few functions implemented for overview but you should map all the original methods.

using FileMocking;
using System;

namespace ConsoleApp
{
    class Program
    {
        static void Main(string[] args)
        {
            string filePath = @"C:\Users\username\Documents\someDoc.txt";
            string directoryPath = @"C:\Users\username\Documents\";

            if (FileSystem.File.Exists(filePath))
            {
                var lines = FileSystem.File.ReadLines(filePath);

                foreach (string line in lines)
                {
                    Console.WriteLine(line);
                }
            }

            if (FileSystem.Directory.Exists(directoryPath))
            {
                var files = FileSystem.Directory.EnumerateFiles(directoryPath);

                foreach (string file in files)
                {
                    Console.WriteLine(file);
                }
            }

            Console.ReadKey();
        }
    }
}

Mocking the FileSystem

In your unit test you can then easily mock FileSystem.File and FileSystem.Directory with other implementations of the IFileSystemProvider and IDirectorySystemProvider interfaces.

using FileMocking;
using System;

namespace ConsoleApp
{
    public class FileSystemProviderMock : IFileSystemProvider
    {
        public bool Exists(string path)
        {
            return true;
        }

        public IEnumerable<string> ReadLines(string path)
        {
            var lines = new List<string>()
            {
                "line1",
                "line2",
                "line3",
                "line4",
            };

            return lines.ToArray();
        }
    }

    public class DirectorySystemProviderMock : IDirectorySystemProvider
    {
        public IEnumerable<string> EnumerateFiles(string path)
        {
            return new List<string>()
            {
                "file1.abc",
                "file2.abc",
                "file3.abc",
            };
        }

        public bool Exists(string path)
        {
            return true;
        }
    }

    class Program
    {
        static void Main(string[] args)
        {
            // Mock your File System!
            FileSystem.File = new FileSystemProviderMock();
            FileSystem.Directory = new DirectorySystemProviderMock();

            string filePath = @"C:\Users\username\Documents\someDoc.txt";
            string directoryPath = @"C:\Users\username\Documents\";

            if (FileSystem.File.Exists(filePath))
            {
                var lines = FileSystem.File.ReadLines(filePath);

                foreach (string line in lines)
                {
                    Console.WriteLine(line);
                }
            }

            if (FileSystem.Directory.Exists(directoryPath))
            {
                var files = FileSystem.Directory.EnumerateFiles(directoryPath);

                foreach (string file in files)
                {
                    Console.WriteLine(file);
                }
            }

            Console.ReadKey();
        }
    }
}

See also

Event handling

In this example I created a Worker class that simply does some work. The class sends a Workstarted event right at the beginning of the work task and a closing WorkCompleted event at the end. The WorkStarted event has the standard EventHandler delegate and the WorkCompleted event has an EventHandler<int> delegate (with generic type int) and will return some int value with the event. Every handler subscribing to the event must match the signature, see the WorkStartedEventHandler and WorkCompletedEventHandler Methods.

Worker class, raises the events

using System;
using System.Threading;

namespace EventHandling
{
    public class Worker
    {
        public event EventHandler WorkStarted;
        public event EventHandler<int> WorkCompleted;

        public void Work()
        {
            this.OnWorkStarted();

            // do your task here
            Thread.Sleep(1000);

            this.OnWorkCompleted(42);
        }

        protected virtual void OnWorkStarted()
        {
            this.WorkStarted?.Invoke(this, null);
        }

        protected virtual void OnWorkCompleted(int eventArgInt)
        {
            this.WorkCompleted?.Invoke(this, eventArgInt);
        }
    }
}

Program class, subscribes / registers to the events and handles them

using System;

namespace EventHandling
{
    class Program
    {
        public static void Main(string[] args)
        {
            Worker worker = new Worker();
            worker.WorkStarted += WorkStartedEventHandler;
            worker.WorkCompleted += WorkCompletedEventHandler;

            worker.Work();

            Console.ReadKey();
        }

        private static void WorkStartedEventHandler(object sender, EventArgs args)
        {
            Console.WriteLine("Work started");
        }

        private static void WorkCompletedEventHandler(object sender, int intArg)
        {
            Console.WriteLine($"Work completed, event argument: {intArg}");
        }
    }
}

More links on the subject

Special paths

Executing path

I sometimes want to get the path of the .exe file that is currently running, usually because I put some folders for logging or configuration there, this is a way to get this path:

// get base directory, entry point
string entryPoint = Assembly.GetEntryAssembly().Location;
string baseDirectory = AppDomain.CurrentDomain.BaseDirectory;

// create directory name
string configDirectory = Path.Combine(baseDirectory, "config");

Console.WriteLine($"entryPoint:      {entryPoint}");
Console.WriteLine($"baseDirectory:   {baseDirectory}");
Console.WriteLine($"configDirectory: {configDirectory}");
entryPoint:      C:\Users\yourUserName\ConsoleApp\bin\Debug\ConsoleApp.exe
baseDirectory:   C:\Users\yourUserName\ConsoleApp\bin\Debug\
configDirectory: C:\Users\yourUserName\ConsoleApp\bin\Debug\config

https://stackoverflow.com/questions/3991933/get-path-for-my-exe

Special folders

Here is a list of all C# Environment.SpecialFolder:

var allSpecialFolders = Enum.GetValues(typeof(Environment.SpecialFolder)).Cast<Environment.SpecialFolder>();

foreach (Environment.SpecialFolder specialFolder in allSpecialFolders)
{
    Console.WriteLine($"{specialFolder.ToString().PadRight(22)} = {Environment.GetFolderPath(specialFolder)}");
}
Desktop                = C:\Users\yourUserName\Desktop
Programs               = C:\Users\yourUserName\AppData\Roaming\Microsoft\Windows\Start Menu\Programs
MyDocuments            = C:\Users\yourUserName\Documents
MyDocuments            = C:\Users\yourUserName\Documents
Favorites              = C:\Users\yourUserName\Favorites
Startup                = C:\Users\yourUserName\AppData\Roaming\Microsoft\Windows\Start Menu\Programs\Startup
Recent                 = C:\Users\yourUserName\AppData\Roaming\Microsoft\Windows\Recent
SendTo                 = C:\Users\yourUserName\AppData\Roaming\Microsoft\Windows\SendTo
StartMenu              = C:\Users\yourUserName\AppData\Roaming\Microsoft\Windows\Start Menu
MyMusic                = C:\Users\yourUserName\Music
MyVideos               = C:\Users\yourUserName\Videos
DesktopDirectory       = C:\Users\yourUserName\Desktop
MyComputer             =
NetworkShortcuts       = C:\Users\yourUserName\AppData\Roaming\Microsoft\Windows\Network Shortcuts
Fonts                  = C:\WINDOWS\Fonts
Templates              = C:\Users\yourUserName\AppData\Roaming\Microsoft\Windows\Templates
CommonStartMenu        = C:\ProgramData\Microsoft\Windows\Start Menu
CommonPrograms         = C:\ProgramData\Microsoft\Windows\Start Menu\Programs
CommonStartup          = C:\ProgramData\Microsoft\Windows\Start Menu\Programs\Startup
CommonDesktopDirectory = C:\Users\Public\Desktop
ApplicationData        = C:\Users\yourUserName\AppData\Roaming
PrinterShortcuts       =
LocalApplicationData   = C:\Users\yourUserName\AppData\Local
InternetCache          = C:\Users\yourUserName\AppData\Local\Microsoft\Windows\INetCache
Cookies                = C:\Users\yourUserName\AppData\Local\Microsoft\Windows\INetCookies
History                = C:\Users\yourUserName\AppData\Local\Microsoft\Windows\History
CommonApplicationData  = C:\ProgramData
Windows                = C:\WINDOWS
System                 = C:\WINDOWS\system32
ProgramFiles           = C:\Program Files
MyPictures             = C:\Users\yourUserName\Pictures
UserProfile            = C:\Users\yourUserName
SystemX86              = C:\WINDOWS\SysWOW64
ProgramFilesX86        = C:\Program Files (x86)
CommonProgramFiles     = C:\Program Files\Common Files
CommonProgramFilesX86  = C:\Program Files (x86)\Common Files
CommonTemplates        = C:\ProgramData\Microsoft\Windows\Templates
CommonDocuments        = C:\Users\Public\Documents
CommonAdminTools       = C:\ProgramData\Microsoft\Windows\Start Menu\Programs\Administrative Tools
AdminTools             = C:\Users\yourUserName\AppData\Roaming\Microsoft\Windows\Start Menu\Programs\Administrative Tools
CommonMusic            = C:\Users\Public\Music
CommonPictures         = C:\Users\Public\Pictures
CommonVideos           = C:\Users\Public\Videos
Resources              = C:\WINDOWS\resources
LocalizedResources     =
CommonOemLinks         =
CDBurning              = C:\Users\yourUserName\AppData\Local\Microsoft\Windows\Burn\Burn

Regex in C#

Remove empty spaces from string

string sentence = " this is      a sentence with many     empty spaces      ";
sentence = sentence.Trim();
sentence = Regex.Replace(sentence, @"\s+", " "):

Regex matching

Regex regex = new Regex(@"@@\S+");
Match match = regex.Match("this is a @@specialword in my string");

if (match.Success)
{
  Console.WriteLine("Regex found: " + match.Value);
}

Test your Regex

A good site to get an overview of what Regex has to offer and directly test it is https://regex101.com/

DateTime

Constructor

DateTime date1 = new DateTime(2010, 8, 18, 16, 32, 0);
Console.WriteLine(date1.ToString());
// The example displays the following output, in this case for en-us culture:
//      8/18/2010 4:32:00 PM

https://docs.microsoft.com/en-us/dotnet/api/system.datetime.-ctor?view=netcore-3.1

ToString() method

In this example I use DateTime.Now.ToString(“yyyyMMdd_HHmmss”) in order to create a dated file Name that will look like this:

20200610091524_myText.txt

string folder = Environment.SpecialFolder.MyDocuments;
string currentDate = DateTime.Now.ToString("yyyyMMdd_HHmmss");
string fileName = Path.Combine(folder, $"{currentDate}_myText.txt");

https://docs.microsoft.com/en-us/dotnet/standard/base-types/custom-date-and-time-format-strings

Starting a GUI application as a console

Console vs. GUI

GUIs are nice and pretty but clicking around is just not what we developpers do… They also bring quite daunting disadvantages:

  • relatively slow
  • cannot be controlled / run in the background
  • painful to automate

As a developper there will come a time where for testing a new cool feature you are going to have to start your GUI, maybe load a configuration, probably click around a few times and wait for your result and this might not sound too bad, but you will do it again and again and again… and again. And this will cost you a lot of time.

Consoles are maybe quite ugly to look at, but they are the right place to look at when you want something to get done efficiently. Do yourself / your users a favor and implement a console version of your program, this will come very handy when you will test your application. Do not let your bad mouse aiming be your bottleneck!

The code

In this implementation you start with a WPF project, you must then create a new WpfApp1.Program class and set it as your Start Object in the project properties. You can then:

  • start the program as a standard GUI application by double-clicking the executable or calling it from the console without parameters: WpfApp1.exe
  • you can also start it as a console application if you pass any parameters: WpfApp1.exe param1 param2 param3

MainWindow.xaml.cs

using System.Windows;

namespace WpfApp1
{
    public partial class MainWindow : Window
    {
        public MainWindow()
        {
            InitializeComponent();
        }
    }
}

Program.cs

using System;
using System.Runtime.InteropServices;

namespace WpfApp1
{
    public static class Program
    {
        [DllImport("Kernel32")]
        public static extern void AllocConsole();

        [DllImport("Kernel32")]
        public static extern void FreeConsole();

        [DllImport("kernel32.dll")]
        static extern bool AttachConsole(uint dwProcessId);

        [STAThread]
        public static void Main(string[] args)
        {
            bool madeConsole = false;
            if (args.Length > 0)
            {
                if (!AttachToConsole())
                {
                    AllocConsole();
                    Console.WriteLine("Had to create a console");
                    madeConsole = true;
                }

                Console.WriteLine("Now I'm a console app!");
                Console.WriteLine("Press any key to exit");
                Console.ReadKey(true);

                if (madeConsole)
                    FreeConsole();
            }
            else
            {
                WpfApp1.App.Main();
            }
        }

        public static bool AttachToConsole()
        {
            const uint ParentProcess = 0xFFFFFFFF;
            if (!AttachConsole(ParentProcess))
                return false;

            Console.Clear();
            Console.WriteLine("Attached to console!");
            return true;
        }
    }
}

see https://stackoverflow.com/questions/29947305/how-to-be-dynamically-either-console-application-or-windows-application

Version handling

Sections of your program version

Software Version is actually an interesting topic, the norm in dotNet seems to be:

Major.Minor.Build.Revision

with different opinions on what Build and Revision are. Microsoft sees Build as a recompilation of the same source but with different processor, platform or compiler change (see https://docs.microsoft.com/en-us/dotnet/api/system.version?view=netframework-4.8), which is a bit confusing because the average programmer would see Build as the Buildnumber of the CI/Buildserver system and Revision as a potential Hotfix for an existing version (see https://softwareengineering.stackexchange.com/questions/24987/what-exactly-is-the-build-number-in-major-minor-buildnumber-Revision). It is probably because Microsoft is a gigantic company which does very serious business on a scale that we cannot grasp, so I will keep Build and Revision as the average programmer sees it.

  • Major: main version, increases for major changes in the program, cannot assure backward compatibility
  • Minor: minor version, increases with new features, backward compatibility can be assumed
  • Build: increases for each Build in the CI / Build server
  • Revision: increases when implementing a hotfix for an older version without adding any new features

Program version in Visual Studio

The version of your program is defined in the AssemblyInfo.cs file. but you can also access it under Assembly Information from your project properties.

  • If Assembly version is not explictly specified, it takes the value of 0.0.0.0.
  • If File version is not explicitly specified, it takes the value of Assembly version.
  • If Product version is not explicitly specified, it takes the value of File version.

You can set a * to let VS increase your version automatically, for example 1.0.* but be careful: the wildcard in 3rd position will be the number of days since the year 2000, and the wildcard in 4th position will be the number of seconds since midnight divided by 2 (see https://stackoverflow.com/questions/826777/how-to-have-an-auto-incrementing-version-number-visual-studio). So 1.0.* is ok if you need a quick solution since the third value will always increase but it will mean nothing.

string assemblyVersion = Assembly.GetExecutingAssembly().GetName().Version.ToString(); 
string fileVersion = FileVersionInfo.GetVersionInfo(Assembly.GetExecutingAssembly().Location).FileVersion; 
string productVersion = FileVersionInfo.GetVersionInfo(Assembly.GetExecutingAssembly().Location).ProductVersion;

Source: http://all-things-pure.blogspot.com/2009/09/assembly-version-file-version-product.html

The Version class

The Version class is the way to go if you want to work with versions in C#, compare them, store them, it is native and convenient.

using System;

namespace ConsoleApp1
{
    public class Program
    {
        public static void Main()
        {
            Version version1 = new Version(1, 2);
            Version version2 = new Version(1, 2, 3, 4);
            Version version3 = new Version("1.2.3.5");

            Console.WriteLine(version1.ToString());
            Console.WriteLine(version2.ToString());
            Console.WriteLine(version3.ToString());

            Console.WriteLine($"version1 < version 2 = {version1 < version2}");
            Console.WriteLine($"version2 > version 3 = {version2 > version3}");
        }
    }
}
1.2
1.2.3.4
1.2.3.5
version1 < version 2 = True
version2 > version 3 = False

Fun fact

The funny thing is that versioning is just a convention, you could do the exact opposite of everybody else and count backwards if you said this is how it works and this would be correct. But you are not a monster so keep things simple and do not confuse your poor users.

Furthermore, if you want to shine in social events with your programmer friends, tell them that each new version number of TeX, the formatting system from Donald Knuth, approaches a bit more Pi than the precedent version (you know 3.1415…). And each new version of METAFONT, the description language used to define raster fonts, also from Donald Knuth, approaches the mathematical constant e (you know, 2.7182…). The mathematician will find this very amusing but the programmer will probably have terrible nightmares.

Logging with NLog

Logging is an absolute must-have feature, which is too convenient and easy to not do it. When I start a new project it is something I will pretty much do on the first days because it brings immediate advantages:

  • for debugging
  • for commenting / understanding code
  • you have an actual document to discuss over with other departments

NLog

NLog is a flexible and free logging platform for various .NET platforms, including .NET standard. NLog makes it easy to write to several targets. (database, file, console) and change the logging configuration on-the-fly.

https://nlog-project.org/

And also very important NLog is licensed under the BSD 3-Clause, which means you can use it for free for private and commercial use on the condition that you write its copyright notice in your documentation. See https://github.com/NLog/NLog/blob/master/LICENSE.txt

In order to log with NLog you will need the following:

  1. NLog binaries
  2. Configuration file
  3. Call to the dll in source Code

See https://github.com/nlog/nlog/wiki/Tutorial

NLog Binaries

You can either add NLog over Nugget in your Visual Studio project or add a specific version of the nlog.dll in your binaries.

See https://www.nuget.org/packages/NLog

Configuration file

I like to have an external dedicated file for it, I usually call it nlog.config and store it with the binaries. Remember to configure File Properties: Copy If newer

<?xml version="1.0" encoding="utf-8" ?>
<nlog xmlns="http://www.nlog-project.org/schemas/NLog.xsd"
      xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">

  <targets>
    <target name="logfile"
            xsi:type="File"
            fileName="${specialfolder:folder=CommonApplicationData}/Company/Product/log/${shortdate}.log"
            layout="${longdate} ${uppercase:${level}} ${message}" />
    <target name="logconsole"
            xsi:type="Console"
            layout="${longdate} ${uppercase:${level}} ${message}" />
    <target name="logfileNamespace"
            xsi:type="File"
            fileName="${specialfolder:folder=CommonApplicationData}/Company/Product/log/${shortdate}_Namespace.log"
            layout="${longdate} ${uppercase:${level}} ${message}" />
  </targets>

  <rules>
    <logger name="*" minlevel="Info" writeTo="logconsole" />
    <logger name="*" minlevel="Info" writeTo="logfile" />
    <logger name="Company.Department.Product.Namespace" minlevel="Trace" writeTo="logfileNamespace" />
  </rules>
</nlog>

https://github.com/nlog/nlog/wiki/Configuration-file

Logging

In order to use NLog you will need to define a Log object first, this should be done in every class where you want to log. and is done with

private static readonly NLog.Logger Log = NLog.LogManager.GetCurrentClassLogger();

Then you will be able to call the following functions:

  • Trace() very high volume of detailed Information, used for developping
  • Debug() more than the default level of Information, used for debugging
  • Info() default level of information, typically used in the field, used for retracing program activity
  • Warn() warnings, these are expected problems that are handled by the program
  • Error() errors, these are somehow expected but the described function/feature cannot continue/fulfill its normal operation
  • Fatal() fatal error, unexpected serious error, it makes no sense to continue program execution
namespace ConsoleApp1
{
    public class Program
    {
        private static readonly NLog.Logger Log = NLog.LogManager.GetCurrentClassLogger();

        public static void Main(string[] args)
        {
            Log.Info("### Starting Program ###");
            Log.Info($"Version: {System.Reflection.Assembly.GetExecutingAssembly().GetName().Version}");
            Log.Info($"Date: {System.DateTime.Now}");
            Log.Info($"OS Description: {System.Runtime.InteropServices.RuntimeInformation.OSDescription}");
            Log.Info($"OS Architecture: {System.Runtime.InteropServices.RuntimeInformation.OSArchitecture}");
        }
    }
}
2020-04-07 22:25:10.8128 INFO ### Starting Program ###
2020-04-07 22:25:10.8592 INFO Version: 1.0.0.0
2020-04-07 22:25:10.8592 INFO Date: 07.04.2020 22:25:10
2020-04-07 22:25:10.8592 INFO OS Description: Microsoft Windows 10.0.18363
2020-04-07 22:25:10.8592 INFO OS Architecture: X64

Multithreading

Multithreading becomes absolutely necessary as soon as you start working with GUI. You cannot let the system freeze and your users asking themselves if the program crashed each time you let a long operation run, that is why you should call async methods. An async method has to declare await in order to free the main thread, otherwise it will run synchronously.

Starting an asynchronous task

using System;
using System.Threading;
using System.Threading.Tasks;

namespace ConsoleApp1
{
    class Program
    {
        static void Main(string[] args)
        {
            Console.WriteLine("Starting Main");

            AsyncMethod("AsyncTask1");
            AsyncMethod("AsyncTask2");

            Console.WriteLine("Main Thread continues doing something else");
            Thread.Sleep(2000);

            Console.WriteLine("Leaving Main");
        }

        private static async Task AsyncMethod(string message)
        {
            await Task.Run(() => LongSyncRunningMethod(message));
        }

        private static void LongSyncRunningMethod(string message)
        {
            Console.WriteLine($"Starting {message}");
            Thread.Sleep(1000);
            Console.WriteLine($"{message} done");
        }
    }
}

One might instinctively think, that LongSyncRunningMethod and its Console.WriteLine($"Starting {message}"); would be called before Console.WriteLine("Main Thread continues doing something else"); but it actually depends on the task scheduler. In some cases it will be true, in other cases not.

Starting Main
Starting AsyncTask1
Main Thread continues doing something else
Starting AsyncTask2
AsyncTask1 done
AsyncTask2 done
Leaving Main

Starting an asynchronous task and getting a return value

using System;
using System.Threading;
using System.Threading.Tasks;

namespace ConsoleApp1
{
    class Program
    {
        static void Main(string[] args)
        {
            Console.WriteLine("Starting Main");

            AsyncCalculation();

            Console.WriteLine("Main Thread continues doing something else");
            Thread.Sleep(2000);

            Console.WriteLine("Leaving Main");
        }

        private static async Task AsyncCalculation()
        {
            int result = await Task.Run(() => LongCalculation());
            Console.WriteLine($"Calculated value: {result}");
        }

        private static int LongCalculation()
        {
            Thread.Sleep(1000);
            return 101;
        }
    }
}
Starting Main
Starting AsyncCalculation
Main Thread continues doing something else
Calculated value: 101
Leaving Main

Catching Exception in an asynchronous task

using System;
using System.Threading;
using System.Threading.Tasks;

namespace ConsoleApp1
{
    class Program
    {
        static void Main(string[] args)
        {
            Console.WriteLine("Starting Main");

            AsyncExceptionMethod();

            Console.WriteLine("Main Thread continues doing something else");
            Thread.Sleep(2000);

            Console.WriteLine("Leaving Main");
        }

        private static async Task AsyncExceptionMethod()
        {
            await Task.Run(() => ThrowException())
                .ContinueWith(task => HandleTaskException(task));
        }

        private static void ThrowException()
        {
            throw new Exception();
        }

        private static void HandleTaskException(Task task)
        {
            if (task.Exception != null)
            {
                Console.WriteLine("Exception caught");
            }
        }
    }
}
Starting Main
Starting AsyncExceptionMethod
Main Thread continues doing something else
Exception caught
Leaving Main

As the task run in another thread, a thrown exception will not be caught in the main thread. The method ContinueWith allows the user to run a check after the task is completed and handle accordingly. In this case ContinueWith accepts a lambda expression with Task as parameter, we can check the status of the completed task, for example if it was completed, cancelled or faulted.

await Task.Run(() => LongCalculation)
    .ContinueWith(task =>
{
    if (task.IsCanceled)
        DoSomethingWhenCancelled();
    else if (task.IsFaulted)
        DoSomethingOnError(task .Exception);
    else 
        // task.IsCompleted
        DoSomethingWhenComplete();
});

https://blog.stephencleary.com/2012/02/async-and-await.html

https://johnthiriet.com/removing-async-void/

Format string

I usually try to comment and log a lot when I write a program, I never regret it. But in order to write information that is actually meaningful you need to get the runtime context, like the current value of some variables or what exception has been thrown. Furthermore you will want to facilitate comprehension for the reader, if you work with hexadecimal values, you will also want to show your value as hexadecimal and spare the reader the conversion. Here are some of the most useful functions for formatting your strings and allowing you to easily write messages that matter and that are comprehensive.

Formatting strings

$ – string interpolation

String Interpolation is available since C# 6, with it you can compose a string with other variables and make your code actually fairly readable for people.

int value = 255;
Console.WriteLine($"The hexadecimal value of {value} is 0x{value.ToString("X")}");
The hexadecimal value of 255 is 0xFF

https://docs.microsoft.com/en-us/dotnet/csharp/language-reference/tokens/interpolated

@ – verbatim string literal

string filename1 = @"C:\Windows\System32\shell32.dll";
string filename2 = "C:\\Windows\\System32\\shell32.dll";
Console.WriteLine(filename1);
Console.WriteLine(filename2);
C:\Windows\System32\shell32.dll
C:\Windows\System32\shell32.dll

https://docs.microsoft.com/en-us/dotnet/csharp/language-reference/tokens/verbatim

Formatting numbers

The ToString() method accepts a string as Parameter, it consist of a letter, big or small and optionally an associated number, which sets how many letters the resulting string should at least have.

X – hexadecimal

int intValue = 255;
// hexadecimal format
Console.WriteLine(intValue.ToString("x"));
Console.WriteLine(intValue.ToString("X4"));
ff
00FF

https://docs.microsoft.com/en-us/dotnet/standard/base-types/standard-numeric-format-strings

F – fixed-point

int intValue = 255;
double doubleValue = 1234.5678;
// fixed-point format
Console.WriteLine(intValue.ToString("F"));
Console.WriteLine(doubleValue.ToString("F"));
Console.WriteLine(doubleValue.ToString("F3"));
255.00
1234.56
1234.567

0 – zero placeholder

double doubleValue = 1234.5678;
// 0 placeholder
Console.WriteLine(doubleValue.ToString("000"));
Console.WriteLine(doubleValue.ToString("00000.00"));
Console.WriteLine(doubleValue.ToString("00000.00000"));
1235
01234,57
01234,56780

https://docs.microsoft.com/en-us/dotnet/standard/base-types/custom-numeric-format-strings

# – digit placeholder

double doubleValue = 1234.5678;
// digit placeholder
Console.WriteLine(doubleValue.ToString("###"));
Console.WriteLine(doubleValue.ToString("#####.##"));
Console.WriteLine(doubleValue.ToString("#####.#####"));
1235
1234,57
1234,5678

Convert – string to int

// base 10 string
string intValue = "64";
Console.WriteLine(Convert.ToInt32(intValue));

// base 16 string
string hexValueAsString = "0x64";
Console.WriteLine(Convert.ToInt32(hexValueAsString, 16));
64
100

https://docs.microsoft.com/de-de/dotnet/api/system.convert.tostring?view=netcore-3.1#System_Convert_ToString_System_Int16_System_Int32_

BitConverter – int to byte array

int value = 511;
byte[] bytes = BitConverter.GetBytes(value);
Console.WriteLine(BitConverter.ToString(byteArray));
127-01-00-00

https://docs.microsoft.com/de-de/dotnet/api/system.bitconverter.getbytes?view=netcore-3.1#System_BitConverter_GetBytes_System_Int32_

string.Join() – Array to string

int[] values = new int[] { 1, 2, 3, 4, 255 };
Console.WriteLine(string.Join(",", values));
Console.WriteLine(string.Join(",", values.Select(b => b.ToString("X2"))));
1,2,3,4,255
01,02,03,04,FF

https://docs.microsoft.com/de-de/dotnet/api/system.string.join?view=netcore-3.1#System_String_Join_System_String_System_Collections_Generic_IEnumerable_System_String__