Author Archives: Pierre Alaplantive

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

Create a PDF written in Markdown with Pandoc

It is possible to create professional Looking PDF written in Markdown with Pandoc. I feel that writing in Markdown is really intuitive for developpers, it is just you with your favorite text Editor (Notepad++ for example), no overwhelming GUI, no formatting, no distractions, just you and your content (if you want to push the no distraction part to the Limit check WriteMonkey). The Problem being Markdown was created for the Internet and has become quite popular, but many of us still live in the old world, where HTML and CSS has no place, the Standards still being Microsoft Office and PDF documents.

“What the hell did you send me?”

My colleagues when I send them something that is neither pdf nor docx

In my opinion the best part is that since you are working only with a text source, you can get an actual diff for your document. As an engineer working in safety, this might be the most important feature, because when a document has been changed, I want to know exactly what has been changed, text, title, table, Image, link… This has been a real pain with regular Tools such as Acrobat Reader. Microsoft Office and even Team Foudation Server! But when you are working with a text source the diff is really easy, for example with WinMerge. It is not pretty since there is no formatting but it is very exact and usually also a great deal faster.

The implementation

One (imperfect) solution to this problem is the conversion from markdown to pdf with pandoc. It uses the LaTeX engine for formatting, so one can add some TeX headers / footers for configuration. You Need to install Pandoc, a TeX engine (I installed MiKTeX) and a bunch of TeX libraries, which will be prompted when you run pandoc. I then run a Batch file from the console to create the final pdf:

:: ========================================= ::
:: PDF Erstellung Markdown -> Pdf mit Pandoc ::
:: ========================================= ::
::
:: This batch file converts a markdown file %1% (.md) into a "pretty" pdf file %2%.
::
:: Markdown:
:: ---------
:: Markdown:     https://github.com/adam-p/markdown-here/wiki/Markdown-Cheatsheet
::
:: Programs needed:
:: ----------------
:: Pandoc:       https://pandoc.org/installing.html
::               https://pandoc.org/MANUAL.html
:: MiKTeX:       https://miktex.org/download
::
:: Remarks:
:: -------
:: - a lot of needed non-standard MiKTeX packets will be automatically downloaded during the first run
:: - the pdf creation is slow, especially when you include images
:: - this batch returns an error if the target pdf file already exists, it is your responsibility to make sure it does not exist
::
:: Online Documentation:
:: ---------------------
:: - https://pandoc.org/MANUAL.html#extension-yaml_metadata_block
:: - https://pandoc.org/MANUAL.html#extension-multiline_tables
:: - https://learnbyexample.github.io/tutorial/ebook-generation/customizing-pandoc/
:: - https://github.com/learnbyexample/learnbyexample.github.io/tree/master/files/pandoc_pdf
:: - https://jdhao.github.io/2019/05/30/markdown2pdf_pandoc/
:: - https://johngodlee.github.io/2019/01/29/float-latex.html
::
:: Example:
:: --------
:: > CreatePdfFromMd SomeMarkdownFile.md PrettyPdf.pdf

pandoc --toc -N^
       --include-in-header style\disablefloat.tex^
       --include-in-header style\inline_code.tex^
       --include-in-header style\bullet_style.tex^
       --highlight-style style\pygments.theme^
       -V colorlinks^
       -V urlcolor=NavyBlue^
       -V toccolor=NavyBlue^
       -V geometry:a4paper^
       -V geometry:margin=2cm^
       -V mainfont="Cambria"^
       --pdf-engine=xelatex^
       -f markdown+yaml_metadata_block+multiline_tables+grid_tables^
       -s %1%^
       -o %2%

:: Erklärung der Parameter:
:: ------------------------
:: --toc                                         Table of Content
:: -N^                                           Number headers
:: --include-in-header style\disablefloat.tex^   Pictures will be added exactly where they are defined (no floating)
:: --include-in-header style\chapter_break.tex^  Each chapter begins on a new page
:: --include-in-header style\inline_code.tex^    Special formatting style for Inline Code (grey background)
:: --include-in-header style\bullet_style.tex^   Bullet style
:: --highlight-style style\pygments.theme^       General style (font, size, color, etc.)
:: -V colorlinks^                                Link/Hyperlink will be colored
:: -V urlcolor=NavyBlue^                         URL blue
:: -V toccolor=NavyBlue^                         Table of content blue
:: -V geometry:a4paper^                          A4 Format
:: -V geometry:margin=2cm^                       Margin 2cm
:: -V mainfont="Cambria"^                        Font "Cambria"
:: --pdf-engine=xelatex^                         used pdf-engine (Xelatex is included in MiKTeX)
:: -f markdown+                                  Source file is markdown
::    yaml_metadata_block+                       Source contains a YAML Meta Datablock (contains information for the cover, see https://pandoc.org/MANUAL.html#extension-yaml_metadata_block)
::    multiline_tables+                          Allow usage of multiline tables (see https://pandoc.org/MANUAL.html#extension-multiline_tables)
::    grid_tables^                               Allow usage of grid tables (see https://pandoc.org/MANUAL.html#extension-grid_tables)
:: -s %1%                                        Source file, Markdown
:: -o %2%                                        Destination file, PDF

Limitations

  • PDF creation must be triggered from the console
  • No live view, little overview for long documents
  • PDF creation relatively long when the document contains images
  • must install Pandoc, MiKTeX
  • LaTeX Header for configuration (I find LaTeX very cryptic)
  • PDF not editable, which means every quick change leads to a new PDF generation

Advantages

  • editing a text file is very fast, no distractions
  • create your Batch file once, use it forever
  • Markdown editing is simple and can be read without creating a pdf
  • pdf looks professional
  • lightweight documents: easy for versioning, easy for comparing
  • include external images: always up-to-date

Links

Markdown syntax

Creating a PDF with Pandoc

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/