The C# Definitive Guide

Are you looking for a learning path to be “fluent” in C#? This is the right place for you! This is my definitive guide to what every beginner and intermediate C# developer should know.

Every intermediate C# developer should know how to productively work with Visual Studio or Visual Studio Code, use async/await keywords, most common LINQ methods and regular expressions. Also, to get around large codebases and be aware of the latest C# features.

1. Environment

Visual Studio is the de-facto Integrated Development Environment (IDE) for C#. Since we will spend most of our workdays with Visual Studio, we should setup Visual Studio to make use more productive.

  • Find a colorscheme. For example, I prefer the Solarized theme
  • Learn the basic shortcuts:
    • Ctrl + Shift + b: Build our solution
    • Ctrl + ,: Navigate to any method in our solution
    • Ctrl + .: Apply a refactor or any action in the current code block
    • Ctrl + q: Search and execute settings or menus of Visual Studio
    • Ctrl + Shift + F12: Go to the next error
  • Install some plugins to make our life easier. Like,
  • Use C# Interactive. We don’t have to create a dummy Console project to try things out. With C# interactive, we have a C# REPL at our disposition. We can load a NuGet package, a dll, or a C# script (.csx file). From Visual Studio, head to View Menu, Other Windows and click C# Interactive.

For more settings and extensions, check my Visual Studio setup for C#.

The C# Definitive Guide
Have everything ready to level up your C#. Photo by Andrew Neel on Unsplash

2. Git and Github

Git

Git is a version control system. A time machine to go back in time, create alternate stories from a point in time and make alternate stories join our present. You got the analogy?

If we are creating a zip file with our code and naming it after the date of our latest change, Git is a better way.

GitHub

Programming is about collaboration. GitHub is the social network for programmers.

With GitHub, we can show our own code, ask for new features in a library, and report bugs in the software we use.

Microsoft, Facebook, Google have some of their own code available on GitHub.

3. Design Patterns and Object-Oriented Design Principles

Desing patterns are recipes to solve common problems in code. This is, given a certain problem, there is a blueprint or an outline that will help us to solve that problem.

  • Recognize some of the most common patterns and learn to use them. For example: Factory, Builder, Composite, Command, Template, Strategy, Null Object, Adapter.
  • Check my take on the Decorator and Pipeline patterns.
  • Learn Uncle Bob’s SOLID principles.

4. Dealing with large codebases

Programming is also about reading code. Get used to navigate throught large codebases.

  • Find a library or a tool you have already used or you find useful. For example, DateTimeExtensions, ByteSize, Insight.Database
  • Where are the unit tests? Do they follow a folder structure? Identify a naming convention for them
  • Does the project follow certain code convention? For example, are braces written on the same line?
  • Grab a copy and compile it yourself
  • Debug a test case scenario for a feature you would like to know about
  • Find out how a feature was implemented

For more guidelines about reading code, check Changelog’s One sure-fire way to improve your coding.

5. Unit tests

A unit test is a “safety net” to make sure we don’t break things when we add new features or modify our codebase. A unit test is a piece of code that uses our code base from a “user” point of view and verifies a given behavior.

6. LINQ

Language-Integrated Query, LINQ, is the declarative way to work with collections in C# or anything that looks like one. Instead of writing foreach, for or while loops to work with collections, let’s give LINQ a try.

7. Regular Expressions

Have you ever used *.txt in the file explorer to find all text files in a folder? If so, we have already used regular expressions. But, *.txt is just the tip of the iceberg.

Regular expressions give us a search syntax to find patterns of text in a string. For example, to find all phone numbers like this one (+57) 3XX XXX-XXX, let’s use (\(\+\d{2}\))\s(\d{3})\s(\d{3})\-(\d{3}).

  • Learn the basics
    • Character sets: [] and [^]
    • Shorthand: \d for digits, \w for alphanumeric chars, \s for whitespace.
    • Repetions: *, ?, {min,max}
    • Any character: the dot .
    • Escape reserved characters: ^$()[]\|-.*+
    • Groups
  • Learn how to match and replace a regex in C#. Take a look at Match, IsMatch, Replace methods in Regex class.
  • Learn how to acess named groups in C#
  • Read Regular expressions’ Quickstart

8. async/await

Asyncronous code is code that doesn’t block when executing long-running operations.

9. New C# features

C# is an evolving language. With every new version, we have more features to write more concise code. These are some of the new features in C# since C# 6.0.

String interpolation

Before we wrote,

string.Format("Hello, {0}", name);

Now we can write,

$"Hello, {name}";

Null-conditional operators

There are two new operators to check for null values: ?? and ?..

Before,

string name = ReadNameFromSomewhere();
if (name == null)
    name = "none";
else
    name.Trim();

After,

string name = ReadNameFromSomewhere();
name?.Trim() ?? "none"

Inlined out variables

Now, we can inline the variable declaration next to the out keyword.

Before,

int count = 0;
int.TryParse(readFromKey, out count);

After,

int.TryParse(readFromKey, out var count)

Or even,

int.TryParse(readFromKey, out _)

Using declarations

A variable preceded by using is disposed at the end of the scope.

Before,

using (var reader = new StreamReader(fileName))
{
    string line; 
    while ((line = reader.ReadLine()) != null)  
    {  
        // Do something  
    }  
}

After,

using var reader = new StreamReader(fileName);

string line; 
while ((line = reader.ReadLine()) != null)  
{  
    // Do something  
}

Nullable reference types

All reference variables are non-nullable by default. Any attempt to dereference a nullable reference gets a warning from the compiler. Goodbye, NullReferenceException!

We need to turn on this feature at the project level in our csproj files.

Before,

int notNull = null;
//  ^^^^^
// error CS0037: Cannot convert null to 'int'
int? canBeNull = null;

string name = null;
SayHi(name);
// ^^^^^
// System.NullReferenceException

void SayHi(string name) => Console.WriteLine(name.Trim()); 

After,

string name = null;
// ^^^^^
// warning CS8600: Converting null literal or possible null value to non-nullable type.

string? canBeNullName = null;
SayHi(name);
// ^^^^^
// warning CS8604: Possible null reference argument for parameter 'name'

To learn other techniques to prevent the NullReferenceException, start checking what the NullReferenceException is and when it’s thrown.

Records

A record is an immutable reference type with built-in equality methods. When we create a record, the compiler creates a ToString method, a value-based equality methods and other methods for us.

public record Movie(string Title, string ReleaseYear);

Top-level statements

All the boilerplate is now gone from Main methods.

Before,

using System;

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

After,

Console.WriteLine("Hello World!");

To learn about other C# features, check my top 10 or so best C# features.

10. Bonus Points

Voilà! That’s my take on what every intermediate C# developer should know! Don’t be overwhelm by the amount of things to learn. Don’t try to learn everything at once, either. Learn one subject at a time! And, start using it in your every day coding as you learn it.

Want to write more expressive code for collections? Join my course, Getting Started with LINQ on Udemy! You'll learn from what LINQ is, to refactoring away from conditionals, and to new methods and overloads from recent .NET versions. Everything you need to know to start working productively with LINQ — in less than two hours.

Happy coding!

How to handle holidays in C# with DateTimeExtensions

Do you need to add only working days to a date in C#? Do you need to check if a date is a holiday? This is how to handle holidays in C# with DateTimeExtensions.

Don’t store holidays on the database

We could have a table in our database with all the holidays in a year and a SQL query to find the holidays between two dates. But, sooner or later we will have to populate that table again. Anyone can forget to do so! Or we will only will do it when it’s too late. There must be a better way!

We can calculate holidays. We don’t need a table in our database with every holiday. Most of the time, we celebrate holidays on a fixed date, the next Monday after the actual date or some days after or before Easter.

Endless water
Cabo San Lucas, Mexico. Photo by Alex Bertha on Unsplash

DateTimeExtensions

DateTimeExtensions has already taken care of handling holidays for us. Good news!

DateTimeExtensions helps us to

  • check if a date is a holiday
  • add working days to a date
  • calculate working days between two dates excluding weekends and holidays

By default, DateTimeExtensions only ignores weekends. To know about holidays, DateTimeExtensions uses cultures. DateTimeExtensions has a list of available cultures.

How to add working days to a date in C#

First, let’s install the DateTimeExtensions NuGet package. We can use the methods AddWorkingDays(), GetWorkingDays() and IsHoliday() to work with holidays.

This is how to add working days to a date in C#.

using System;
using DateTimeExtensions;
using DateTimeExtensions.WorkingDays;
                    
public class Program
{
    public static void Main()
    {
        var fridayBeforeHoliday = new DateTime(2018, 10, 12);
        
        // Add two days including Saturday and Sunday
        var plusTwoDays = fridayBeforeHoliday.AddDays(2);
        Console.WriteLine(plusTwoDays);
        
        // Add two days without Saturdays and Sundays
        var withoutHoliday = fridayBeforeHoliday.AddWorkingDays(2);
        //                                       ^^^^^
        Console.WriteLine(withoutHoliday);
        
        // Add two days without weekends and holidays
        // For example, Oct 15th is holiday in Colombia
        var tuesdayAfterHoliday = fridayBeforeHoliday.AddWorkingDays(2, new WorkingDayCultureInfo("es-CO"));
        //                                            ^^^^^
        Console.WriteLine(tuesdayAfterHoliday);

        // Check if Oct 15 is holiday
        var holiday = new DateTime(2018, 10, 15);
        var isHoliday = holiday.IsHoliday(new WorkingDayCultureInfo("es-CO"));
        //                      ^^^^^
        Console.WriteLine(isHoliday);
    }
}

Notice, AddDays() doesn’t take into account weekends or holidays. And, by default, DateTimeExtensions AddWorkingDays() only ignores weekends.

To ignore holidays, we need to pass a culture to AddWorkingDays(). In our previous example, we used the Colombian culture, es-CO, to ignore holidays.

Voilà! That’s how you can handle holidays in C# and avoid adding a table in your database with all holidays. DateTimeExtensions has also other useful extensions methods to the DateTime and DateTimeOffset classes.

I contributed to DateTimeExtensions adding support for holidays in Colombia. Do you want to contribute to an open source project? Add holidays for your own country!

If you want to read more content, check my C# Definitive Guide with the subjects every intermediate C# developer should know and my quick guide to LINQ with all you need to know about LINQ in 15 minutes or less.

Happy coding!

When logging met AOP with Fody

How many times have you had to log the entry and the exit of every single method in a service or in a class? So, your code ends up entangled with lots of Log.XXX lines. Something like this:

abstract class Beverage
{
    private int _capacity = 10;

    public int Drink(int count)
    {
        Log.Info($"Init {nameof(Drink)}");

        try
        {
            if (count > _capacity)
                throw new ArgumentException("Not enouhg beers");
        }
        catch (Exception e)
        {
            Log.Error(e);

            throw;
        }

        Enumerable.Range(1, count)
                  .ToList()
                  .ForEach(t => Drinking(t));

        _capacity -= count;

        Log.Info($"Exit {nameof(Drink)}: {_capacity}");

        return _capacity;
    }

    public abstract void Drinking(int soFar);
}

class Beer : Beverage
{
    public override void Drinking(int current)
    {
        Log.Info($"Init {nameof(SomeMethod)}");

        // I have drunk {current} beers so far
        SomeMethod();

        Log.Info($"Exit {nameof(SomeMethod)}");
    }

    private void SomeMethod()
    {
        Log.Info($"Init {nameof(SomeMethod)}");
        // Do some logic here
        Log.Info($"Exit {nameof(SomeMethod)}");
    }
}

But, there are a couple of problems with this approach. First, the code is less readable and full of boilerplate code. Second, there is no separation between generic code and problem specific code. Last, any developer could forget to log new methods or get tired of logging things at all. Happy debugging, later on!

Wait!. Life is too short to log our code like that. There must be a smarter way. AOP (Aspect Oriented Programming) to the rescue! AOP can help you to cache things, to retry actions, and, in general, to isolate boilerplate code from our codebase.

You could use MethodBoundaryAspect.Fody. This is an add-in of Fody, a free (gratis and libre) AOP library. It logs the entry, the exit and the exceptions of every method in your class. To start using Fody, first add MethodBoundaryAspect.Fody nuget. Then, create a custom attribute inheriting from OnMethodBoundaryAspect to do all the logging.

public sealed class CustomLogAttribute : OnMethodBoundaryAspect
{
    public override void OnEntry(MethodExecutionArgs args)
    {
        Log.Info($"Init: {args.Method.DeclaringType.FullName}.{args.Method.Name} [{args.Arguments.Length}] params");
        foreach (var item in args.Method.GetParameters())
        {
            Log.Debug($"{item.Name}: {args.Arguments[item.Position]}");
        }
    }

    public override void OnExit(MethodExecutionArgs args)
    {
        Log.Info($"Exit: [{args.ReturnValue}]");
    }

    public override void OnException(MethodExecutionArgs args)
    {
        Log.Error($"OnException: {args.Exception.GetType()}: {args.Exception.Message}");
    }
}

And finally, annotate your class.

abstract class Beverage
{
    private int _capacity = 10;

    public int Drink(int count)
    {
        // All log statements removed...
    }

    public abstract void Drinking(int soFar);
}

[CustomLog]
class Beer : Beverage
{
    public Beer(string name) : base(name, 10)
    {
    }

    public override void Drinking(int current)
    {
        // I have drunk {current} beers so far
        SomeMethod();
    }

    private void SomeMethod()
    {
        // Do some logic here
    }
}

That’s it, you have logged your class with a single attribute, instead of lots Log.XXX. Your code is more readable and straight to the point. Now, it’s time to go and try to remove all boilerplate code from our code base.