Skip to content

C#

To-do

  • Sort out Events section, in particular the example cited
  • Develop C# implementation of CSV parser. This appears to be harder than it should be, apparently because the object returned by the CsvReader.GetRecords<T>() method is an IEnumerable which is one of the confusing points of the UWP course. YouTube tutorials do not seem helpful..

Variables

Parsing

Data types can be used as static classes, exposing a TryParse method.

parsedInt = Int32.TryParse(rawInt);
parsedDate = DateTime.TryParse(rawDate);

String

Specify a verbatim literal string by prepending @, which disables escape characters and forces interpretation of backslashes literally:

string filePath = @"C:\televisions\sony\bravia.txt";

Specify a formatted string by prepending a $

int n;
string s = $"{n} is a number";
Standard numeric format strings are used to format common numeric types. They take the form of a character (i.e. C for currency, N for number) followed by a number. They can be passed as arguments to the ToString method of the literal or in the placeholder of a formatted string after :.
Console.WriteLine($"{123.456789:C }");          // $123.46
Console.WriteLine(123.456789d.ToString("C"));   // $123.46
A precision specifier can define the number of fractional digits after the decimal separator.
Console.WriteLine($"{123.456789:C3 }"); // $123.457
Empty space can be added to either side of the value to create evenly spaced output by placing a number after a comma (positive for right-alignment, negative for left-alignment):
Console.WriteLine($"{123.456789, 15}");

Casting

Because real numbers are stored as doubles by default, in order to assign to a float variable you must append f to the literal:

float num = 3.14f;
A similar logic pertains for integers to be declared as doubles:
double num = 3d;
decimal data type literals, which have 28-29 significant digits, can be declared with the m suffix
decimal num = 123.4567890123456789m
char literals can be encoded in Unicode:
char umlaut = '\u00F6';
Variables can be explicitly cast to some other data types by placing the new data type in parentheses before the value:
int pi = (int)System.Math.PI;
This casting won't work with string, which can be cast by using the Convert type or parsed using the data type's Parse method. The differense is that using Convert will return a 0 if the value is null while Parse will throw an exception.
w = "5";
int wConverted = System.Convert.ToInt32(w);
int wParsed = int.Parse(w);

Collection

Arrays

Arrays are declared differently from built-in arrays in C++.

int[] primes = {1, 2, 3, 5, 7, 11, 13, 17, 19, 23};
int primes[10] {1, 2, 3, 5, 7, 11, 13, 17, 19, 23}

An empty array must still have its size declared

int[] primes = new int[10];
An unnamed array:
new[] {1, 2, 3};
Arrays can be traversed with a foreach loop, but the elements can not be changed.:
foreach (var i in container)
{
    // ...
}

Arrays can be copied with the Clone() and Copy() methods: see ArrayCloning.

Arrays can be reversed in place with the Reverse method.

int[] primes = {1, 2, 3, 5, 7, 11, 13, 17, 19, 23};
Array.Reverse(primes);

LINQ

Language Integrated Query (LINQ) refers to a C# library that facilitates querying of collections. These are exposed as extension methods: methods that are available on already existing queryable types This means extension methods are exposedon existing collection types like Array and List because they are derived from IEnumerable<T>, and thus need no modification to serve as a LINQ data source.

Linq methods are available in two semantically identical syntaxes: query syntax and method syntax (also lambda syntax). Query syntax is meant to be more intuitive for developers familiar with SQL. Method syntax allows method chaining.

int[] numbers = { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 };
var evens = from n in nums where n % 2 == 0 orderby n descending select n;
int[] numbers = { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 };
var evens = nums.Where(n => n % 2 == 0).OrderByDescending(n => n);
numbers = [1,2,3,4,5,6,7,8,9,10]
evens = [n for n in numbers if n % 2 == 0]
list(reversed(evens))

Notably, unlike loop structures in C#, LINQ methods can work on unordered collections like Dictionaries.

ObservableCollection

The ObservableCollection class is used to define collections that provide notifications to data bindings when items are added or removed. As such, it is used in GUI programming...

DateTime

DateTimeOffset is preferred over DateTime because it includes an offset value that indicates the timezone.

Parsing

DateTime is a class that exposes several static methods of parsing raw values. All of them have overloads that accept CultureInfo objects (implementing IFormatProvider) which can affect parsing of ambiguous dates.

  • Parse() will attempt to parse a string and raise a FormatException if unable to do so.
  • ParseExact() requires an exact string template and requires a CultureInfo object as well.
string rawDate = "07/04/1776";
try 
{
    DateTime parsedDate = DateTime.Parse(rawDate);
}
catch (FormatException)
{
    Console.WriteLine("Unparsable!")
}
Console.WriteLine(parsedDate.ToLongDateString()); // => "July 4, 1776"
string rawDate = "07/04/1776";
try 
{
    DateTime parsedDate = DateTime.Parse(rawDate, CultureInfo.GetCultureInfo("en-GB"));
}
catch (FormatException)
{
    Console.WriteLine("Unparsable!")
}
Console.WriteLine(parsedDate.ToLongDateString()); // => "April 7, 1776"
string rawDate = "07/04/1776";
try 
{
    DateTime parsedDate = DateTime.ParseExact(rawDate, "M/d/yyyy", CultureInfo.InvariantCulture);
}
catch (FormatException)
{
    Console.WriteLine("Unparsable!")
}
Console.WriteLine(parsedDate.ToLongDateString()); // => "July 4, 1776"

TryParse() returns no value, but takes an out parameter. It does not throw an exception if the date is unparsable, but rather outputs the default date January 1, 1 AD. The overload that accepts a Culture object also requires a DateTimeStyles object.

string rawDate = "07/04/1776";
DateTime parsedDate;
DateTime.TryParse(
    rawDate, 


    out parsedDate
);
Console.WriteLine(parsedDate.ToLongDateString()); // => "July 4, 1776"
string rawDate = "07/04/1776";
DateTime parsedDate;
DateTime.TryParse(
    rawDate, 
    CultureInfo.GetCultureInfo("en-GB"), 
    DateTimeStyles.None, 
    out parsedDate
);
Console.WriteLine(parsedDate.ToLongDateString()); // => "April 7, 1776

ParseExact() TryParseExact()

Timezones

TimeZoneInfo includes static methods that can access system timezones. DateTime does not include timezone information, so it must be specified at runtime.

TimeZoneInfo sidneyTimeZone = TimeZoneInfo.FindSystemTimeZoneById("E. Australia Standard Time");
var sydneyTime = TimeZoneInfo.ConvertTime(DateTime.Now, sydneyTimeZone);

Enumerating all system timezones.

foreach (var timeZone in TimeZoneInfo.GetSystemTimeZones())
{
    Console.WriteLine(timeZone.GetUtcOffset());
}

Methods

Lambda

A lambda expression can have two forms, both of which use the lambda declaration operator =>

(input-parameters) => expression
(input-parameters) => { statements }

Anonymous event handlers can be reformulated as lambdas to reduce code complexity.

SubmitButton.Click += delegate(object sender, EventArgs e)
{
  MessageBox.Show("Button Clicked");
}

// Using a (statement) lambda:
SubmitButton.Click += (s,e) => MessageBox.Show("Button Clicked");

ref

ref allows variables that are normally passed by value to be passed by reference.

Integers are normally passed by value, so number will not change

static void Main() 
{
    int number = 0;
    plusOne(number);
}
static void plusOne(int n) 
{
    n++;
}

Now number will increment by one because it is being passed by reference.

static void Main() 
{
    int number = 0;
    plusOne(ref number);
}
static void plusOne(ref int n) 
{
    n++;
}

Alternatively, number can be reassigned a variable if the method is refactored to return the new value.

static void Main() 
{
    int number = 0;
    number = plusOne(number);
}
static int plusOne(ref int n) 
{
    n++;
    return n;
}

out

out allows a method to assign a value to a variable that has no value yet. It can be used to return multiple values.

static void Main()
{
    double n = 5;
    double nSquared;
    square(n, out nSquared);
    Console.WriteLine($"{n} ^ 2 = {nSquared}");
}
static void square(double x, out double y)
{
    y = System.Math.Pow(x, 2);
}

out is prominently used in the TryParse method.

params

params allows you to process a variable number of similarly-typed arguments in the method signature. This collection of arguments is abstracted as an array.

static void method(int[] args)
{
    foreach (int el in args) 
    {
        Console.WriteLine(el);
    }
}
If the arguments to be accepted are themselves arrays, then you must define an array of arrays (ex.). This technique may not have worked in previous versions of C#.
static void method(int[][] args)
{
    foreach (var array in args){
        foreach (int el in array) {
            Console.WriteLine(el);
        }
    }
}

Delegates

Delegates are a functional programming feature in C# that facilitate loose coupling. They allow a function to be abstracted so that updated logic can be implemented without incurring technical debt.

Delegates take the form of a method signature using the delegate keyword. One or more methods implementing the delegate can be formulated which do not reference the delegate in any way, shape, or form, except for the fact that their method signature matches that specified by the delegate.

Where the method is to be used, instead of calling the method directly, the delegate is instantiated like an object, but the name of the specific method that implements the delegate is passed as a parameter. The instantiated delegate can then be called, which passes the parameters to the method. This results in looser coupling because when changing implementation, only the parameter specifying the improved method needs to be adjusted, and the delegate ensures that the same pattern of parameters is enforced at compile-time.

Delegates can be used for messaging in .NET and especially to tie events to event handlers, but they are no longer used as much as Func<T,TResult> and Action<T>.

public delegate void InformationNeeded(int n, string s);

static void Main()
{
    InformationNeeded form = new InformationNeeded(SimpleReport)

    // ...

    form(2, "kiwi");
    form(3, "jackfruit");
}

void SimpleReport(int m, string t)
{
    Console.WriteLine($"int: {m}, string: {t}");
}
public delegate void InformationNeeded(int n, string s);

static void Main()
{
    InformationNeeded form = new InformationNeeded(BetterReport)

    // ...

    form(2, "kiwi");
    form(3, "jackfruit");
}

void BetterReport(int m, string t)
{
    Console.WriteLine($"There are {m} items of type {t}");
}

Events

Events signal the occurrence of an action or notification. They are raised or fired (invoked) by the publisher and received by the event handler or subscriber. They represent a syntactic sugar over the delegate structure, which is used in the background as the pipeline to connect publisher and handler.

The simplest way to define an event, using the builtin EventHandler type, is as follows:

public event EventHandler Occurrence;

In actuality, EventHandler is itself a wrapper around a delegate, and any delegate can be wrapped by the event by the delegate's name as the event's data type:

public delegate void InformationNeeded(int n, string s);

class Form
{
  public event InformationNeeded FormEvent;
}

But because the event structure requires an object reference, the simplest implementation for raising an event is more involved. This is because the Main entry-point for C# programs is static, and not an object instance.

The event must be defined within a class that is then instantiated. The event is implemented in an event handler that is a method within the same class that defines the event. After first checking if the event is null (abbreviated syntax using the null-conditional member access operator is equivalent) the event object is called.

Here, the event handler is called by the constructor itself.

namespace SimpleEvent
{

  class Program
  {
    static void Main(string[] args)
    {
      TriggeringEvent eventTrigger = new TriggeringEvent();
    }

    public class TriggeringEvent
    {
      public event EventHandler Event;

      public TriggeringEvent()
      {
        OnEvent(this, EventArgs.Empty);
      }

      protected virtual void OnEvent(object s, EventArgs e)
      {
        var newEvent = Event as EventHandler;

        if (newEvent != null)
        {
          newEvent(this, EventArgs.Empty);
        }
        // Null-conditional operator available since C# 6:
        // newEvent?.Invoke(this, EventArgs.Empty);
      }
    }
  }
}

If the method signature of the event handler is made public, then the event can be raised externally and called like any other method, and a slightly simpler example can be constructed.

namespace SimpleEvent
{
  class Program
  {
    static void Main(string[] args)
    {
      TriggeringEvent eventTrigger = new TriggeringEvent();
      eventTrigger.OnEvent(eventTrigger, EventArgs.Empty);
    }

    public class TriggeringEvent
    {
      public event EventHandler Event;

      public virtual void OnEvent(object s, EventArgs e)
      {
        Console.WriteLine("OnEvent");
        var newEvent = Event as EventHandler;
        newEvent?.Invoke(this, EventArgs.Empty);
      }
    }
  }
}
Conventionally, however, the event handler is not made public, but defined using the protected virtual void method signature.

Event wiring refers to the process of adding subscribers to an event. In implementation, this involves adding the subscribers to the invocation list of the delegate that is used to tie the event to event handler.

Event += EventSubscriber;

In actuality, this syntax uses delegate inference, where the compiler automatically determines the correct delegate to use. The fuller syntax avoiding the use of this feature would be

Event += new EventHandler(EventSubscriber);

The event is then fired by calling it, but this can only occur from within the type in which it is defined. So it has to be fired from within another of that type's methods.

Anonymous methods and lambdas can also be used after the += operator:

Event += (s,e) => Console.WriteLine("Subscribing to event!");

In this example , adapted from a Pluralsight course, the OnMissionAccomplished and OnMissionStatusReport event handlers send two different types of events, respectively: MissionStatusReport and MissionAccomplished. Even though both of these events are EventHandler types, they are actually events.

Async

The async modifier is used to construct asynchronous code. By convention, asynchronous methods are named with "Async" to distinguish them. The await keyword marks the variable containing the result.

public int Addition()
{
  var a = SlowMethodOne();
  var b = SlowMethodTwo();
  return a + b;
}
public async Task<int> AdditionAsync()
{
  var a = SlowMethodOneAsync();
  var b = SlowMethodTwoAsync();
  return await a + await b;
}

Return types used for async include: - Task - Task<T> - Void should generally be avoided with the exception of event handlers

Async does not create new threads by default, so it is only suitable for UI and IO-bound methods, not CPU-bound methods.

Here async is used to return an enumerable collection of Customer objects from the file IO system.

public class CustomerDataProvider
{
  private static readonly string _customersFileName = "customers.json";
  private static readonly StorageFolder _localFolder = ApplicationData.Current.LocalFolder;
  public async Task<IEnumerable<Customer>> LoadCustomersAsync()
  {
    var storageFile = await _localFolder.TryGetItemAsync(_customersFileName) as StorageFile;
    List<Customer> customerList = null;
    // ...
  }
}

Here threads are used to handle JSON files and data on application load in the data provider for a GUI application

public async Task<IEnumerable<Customer>> LoadCustomersAsync()
{

}
public async Task SaveCustomersAsync(IEnumerable<Customer> customers) 
{ 

}

Exceptions

Exceptions expose Message and StackTrace attributes that can be inspected for further information (ref. ExceptionHandling)

Member access operators are syntactic sugars that allow operations to be performed without exception handling.

Operator Name Description
=> Lambda declaration operator
?. Null-conditional member access operator Applies the operation to its operand only if it evaluates to non-null, otherwise it returns null
?[] Null-conditional element access operator Applies the operation to its operand only if it evaluates to non-null, otherwise it returns null
?? Null-coalescing operator Returns value of left-hand operand if non-null, otherwise returns result of right-hand operand.
??= Null-coalescing assignment operator Assigns the value of the right-hand operand to the left-hand operand, only if the left-hand operand evaluates to null.

Classes

Access modifiers

Classes can be declared with various access modifiers that affect the compiler's behavior. These are intended to prevent what would be runtime errors by turning them into compile-time errors, improving code quality. - static prevents instantiation - abstract indicates the class is to be completed in a derived class. Every method marked as abstract has to be implemented in the derived class, and the class has to be marked with abstract as well. - sealed prevents inheritance - partial allows the same class to be defined across multiple files

Constructor

If not defined, the compiler will provide a default constructor.

A constructor can be overloaded by using the this keyword in the constructor's signature after a colon, as if invoking the second constructor:

using System;

class Car
{
    public string brand { get; set; }
    public Car() : this("Ford") { }
    public Car(string brand)
    {
        this.brand = brand;
    }
}

class Program
{
    static void Main(string[] args)
    {
        Car ford = new Car();
        Console.WriteLine(ford.brand); // => Ford
    }
}

Properties

A property protects the data of a private variable ("field") by implementing getter and setter accessor functions. These allow data validation or other logic to be performed when the variable is changed. The private variable being protected is called the backing store.

By convention, properties have identifiers in title case. The identifier for the backing field of a property is conventionally the same as the property, except lowercase or prepended with an underscore.

In the set accessor, the keyword value is used for the argument passed in.

private string name; // field
public string Name   // property
{
    get { return name; }
    set { this.name = value; }
}
A common shorthand was introduced in C# 3 called automatically implemented properties, where the p
public string Name { get; set; }
The set accessor uses an implicit parameter value, whose value is the type of the property.
class Person
{
  private string _name; // the name field
  public string Name    // the Name property
  {
    get => _name;
    set => _name = value;
  }
}
Data validation for setter accessor:
public class Date
{
  private int _month = 7;  // Backing store

  public int Month
  {
    get => _month;
    set
    {
      if ((value > 0) && (value < 13))
      {
        _month = value;
      }
    }
  }
}
A property can be made read-only by simply removing the setter.

An access modifier can also be applied to only one or the other of the accessors to enforce encapsulation. This can make the property read-only externally while still allowing the class's own logic to change the property's value:

private set
{
    if ((value > 0) && (value < 13))
    {
        _month = value;
    }
}
Similarly, fields can be modified with the readonly access modifier. This will prevent the variable from being changed in external code as well as in any internal methods. Readonly fields can only be set by the constructor or variable initializers.

Static classes

Classes marked with static are not instantiated. An example is the System.Console class, which is never instantiated even though its methods are available for use. This structure is called a singleton and is useful as a container for assorted utilities.

Methods marked with static are independent of the class instance itself, and as such do not have access to fields that are not const.

Polymorphism

Modifiers like abstract, virtual, and override allow derived classes to implement logic that builds upon that of a base class.

  • virtual allows you to declare methods and properties in a base class which can be overriden in a derived class. virtual cannot be used with static, abstract, private, or override.
  • abstract is similar, except that the class itself must also be marked as an abstract class, preventing instantiation of the base class. Instead of defining a base function, only the signature is declared.
  • In both cases, override is used to mark the implementation in the derived class.

abstract class Shape
{
    public abstract double GetArea();
}
class Shape
{
    public virtual double GetArea()
    {
        return;
    }
}

class Rectangle : Shape
{
    public double Length { get; set; }
    public double Width { get; set; }

    public Rectangle()
    {
        Length = 2;
        Width = 3;
    }

    public override double GetArea()
    {
        return Length * Width;
    }
}
class Circle : Shape
{
    public double Radius { get; set; }

    public Circle()
    {
        Radius = 3;
    }

    public override double GetArea()
    {
        return System.Math.PI * System.Math.Pow(Radius, 2);
    }
}

Interfaces

Interfaces can be used to break up dependencies and implement the dependency inversion principle. This principle holds that components should be dependent on abstractions, and not on implementations. (src)

Interfaces contain property and method definitions that must be implemented in derived classes, and as such are similar in concept to abstract classes. Like abstract classes, an interface may not be instantiated. Unlike abstract classes, the override keyword is not used on classes that implement interfaces, and access modifiers are not acceptable for interface members. Also unlike abstract classes, smplementation of interface members is mandatory. And although a derived class can only inherit from a single base class, there is no limit on the number of interfaces that a derived class can inherit from.

Interface identifiers conventionally with the capital I.

interface IAnimal
{
    void AnimalSound();
}
class Pig : IAnimal
{
    public void AnimalSound()
    {
        Console.WriteLine("Oink");
    }
}

Notably, a commonly encountered interface is IEnumerable because both Lists and Arrays implement it. So methods that iterate over either Lists or Arrays typically use IEnumerable to accept either data type.

Attributes

Attributes appear to resemble Python decorators because like decorators appear on the line preceding a function or class definition, but they appear to be used for something else. Attributes in C# are used to adjust the function of code in a variety of ways.

ObsoleteAttribute will produce a compiler warning or error (preventing compilation entirely) when deprecated code is being used.

[Obsolete("Don't use this class anymore, instead use ...")]
class Cow { }

static void Main()
{
    Cow betsy = new Cow();
}
[Obsolete("Don't use this class anymore", true)]
class Cow { }

static void Main()
{
    Cow betsy = new Cow();
}

A family of attributes exist to assist debugging.

DebuggerStepThrough can decorate certain methods to be stepped through or skipped while debugging. This is useful for situations where only some properties of a class have to be debugged. This allows more controlled debugging than using the "Step over properties and operators" setting in Debugging Options. (src)

using System.Diagnostics;

struct Cow 
{
    public string Name { [DebuggerStepThrough] get { return "Bessy"; } }
    public int Weight { get { return 5; } }
}

static void Main()
{
    Cow betsy = new Cow();
}

DebuggerDisplayAttribute allows an object's state to be formatted to be more understandable in the debugger's watch window. (src)

using System.Diagnostics;

[DebuggerDisplay("{Name} weighs {Weight} lbs")]
struct Cow
{
    public string Name;
    public int Weight;
}

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

        // This triggers instantiation of the attribute
        typeof(Program).GetCustomAttributes(false);

        Cow betsy = new Cow { Name = "Betsy", Weight = 1000 };
        Console.WriteLine($"{betsy.Name} weighs {betsy.Weight} lbs");
    }
}

The CallerMemberNameAttribute can be added to string parameters in functions that are meant to process the name of the function calling them. This avoids the verbosity of placing nameof() on every invocation. (src)

using System.ComponentModel;
using System.Runtime.CompilerServices;

namespace WiredBrainCoffee.UWP.Models
{
    public class Customer : INotifyPropertyChanged
    {
        private string firstName;

        public string FirstName
        {
            get => firstName;
            set
            {
                firstName = value;
                OnPropertyChanged(nameof(FirstName));
            }
        }
        public string LastName { get; set; }
        public bool IsCoffeeDrinker { get; set; }

        public event PropertyChangedEventHandler PropertyChanged;

        private void OnPropertyChanged([CallerMemberName]string propertyName = null)
        {
            PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
        }
    }
}

In XAML, the ContentPropertyAttribute attribute is used to define whether or not a control accepts a default Content property field

using Windows.UI.Xaml;
using Windows.UI.Xaml.Controls;
using Windows.UI.Xaml.Markup;

[ContentProperty(Name = nameof(Customer))]
public sealed partial class CustomerDetailControl : UserControl
{
    public CustomerDetailControl()
    {
        this.InitializeComponent();
    }
}

Files

Streams

A Stream is an abstraction of a backing store, or sequence of bytes, which can be a file, an input/output device, a websocket, or an inter-process communication pipe. The Stream class itself is an abstract base class that can't be instantiated. FileStream is the concrete class that uses files as its backing store.

Streams can support seeking, although network streams do not support seeking. This can be checked by calling the stream's boolean CanSeek property.

Stream implements IDisposable, which means it can be disposed indirectly by being placed in a using block.

A bit bucket is a stream with no backing store and is implemented as Stream.Null.

Testing

Tests are usually organized in a separate project that is linked to the project containing the system under test (SUT).

Visual Studio has a built-in test-runner, but the dotnet CLI utility also allows the entire test suite to be executed from the command-line.

dotnet test

.NET supports several test frameworks.

xUnit

In xUnit, tests are organized into public classes, and test cases are composed by individual methods on this class, decorated with the Fact attribute. Test assertions are made with static Assert method calls. (src)

public class TestCases
{
    [Fact]
    public void TestCase()
    {
        Assert.Equal(2 + 2, 4);
    }
}

Assertions that an exception must be thrown are generic method calls typed to the specific exception.

public class StarshipDeploymentShould
{
    [Fact]
    public void ThrowOnNullValidator()
    {
        var sut = new StarshipDeployment(null);
        Assert.Throws<ArgumentNullException>(sut);
    }
}

Test fixtures can be formed on properties of the main test class. They must be initialized with the test class's constructor.

public class DeskBookerRequestProcessorTests
{
    public DeskBookerRequestProcessor processor { get; set; }

    public DeskBookerRequestProcessorTests()
    {
        processor = new DeskBookerRequestProcessor();
    }

    [Fact]
    public void ShouldReturnDeskBookerResultWithRequestValues()
    {
        var request = new DeskBookerRequest
        {
            FirstName = "Thomas",
            LastName = "Huber",
            Email = "thomas@huber.com",
            Date = new DateTime(2020, 1, 28)

        };
        var result = processor.BookDesk(request);

        Assert.NotNull(result);
        Assert.Equal(request.FirstName, result.FirstName);
        Assert.Equal(request.LastName, result.LastName);
        Assert.Equal(request.Email, result.Email);
        Assert.Equal(request.Date, result.Date);

    }

    [Fact]
    public void ShouldThrowExceptionIfRequestIsNull()
    {
        var exception = Assert.Throws<ArgumentNullException>(() => processor.BookDesk(null));
        Assert.Equal("request",exception.ParamName);
    }
}
namespace DeskBooker.Core.Processor
{

    public class DeskBookingResult
    {
        public string FirstName { get; set; }
        public string LastName { get; set; }
        public DateTime Date { get; set; }
    }

    public class DeskBookingRequest
    {
        public string FirstName { get; set; }
        public string LastName { get; set; }
        public DateTime Date { get; set; }
    }

    public class DeskBookingRequestProcessor
    {
        public DeskBookingResult BookDesk(DeskBookingRequest request)
        {
            return new DeskBookingResult
            {
                FirstName = request.FirstName,
                LastName = request.LastName,
                Date = request.Date,
            };
        }
    }
}

In this example, PersonProcessor is a public class whose constructor takes an ISqlDataAccess data provider by means of dependency injection. The LoadData method is setup, and the mocked object is instantiated with the Create method call. The mock will inject the mock data provider, which returns a List<PersonModel>.

using (var mock = AutoMock.GetLoose())
{
    mock.Mock<ISqliteDataAccess>()
        .Setup(x => x.LoadData<PersonModel>("SELECT * FROM Person"))
        .Returns(GetSamplePeople());
    var sut = mock.Create<PersonProcessor>();
    var expected = GetSamplePeople();
    var actual = sut.LoadPeople();
    Assert.True(actual != null);
    Assert.Equal(actual.Count,expected.Count);
}

The Theory attribute decorates a parameterized test and commonly appears in conjunction with InlineData attributes that contain the parameter values.

using System;
using Xunit;
using System.Linq;

namespace MathTests
{
    public class MathWorks
    {
        [Theory]
        [InlineData(2,2)]
        [InlineData(3,3,3)]
        [InlineData(1, 2, 3, 4, 5, 6, 7, 8, 9, 10)]
        public void Addition(params int[] ops)
        {
            int loopsum = 0;
            foreach (int i in ops)
            {   
                loopsum += i;
            }
            int linqsum = ops.Sum();
            Assert.Equal(loopsum,linqsum);
        }
    }
}

The xUnit test-runner can be modified using a JSON file named xunit.runner.json. This file must be copied to the output directory by selecting "Copy if newer" in the file's properties.

This example will display the method names only, rather than the fully-qualified dotted name with namespace and class.

{
    "methodDisplay": "method"
}

Moq

Moq ("mock-you") is an open-source mocking library available as a NuGet package. Mock objects are generics that take the abstract base class or interface used by the mocked object (see provider pattern). Naturally, this means the concrete objects they are replacing must also be implementing those interfaces.

There are two mock modes, strict and loose. By default, mock objects are loose, which means they will return default type values and not throw any exceptions to methods that have not been setup.

var mock = new Mock<IMockTarget>();
var mock = new Mock<IMockTarget>(MockBehavior.Strict);

Mock properties require setup.

mock.Setup(x => x.Property).Returns("Hello, world!");

Methods of mock objects also require setup using an identical syntax. Concrete arguments can be provided, but preferable is using argument matching. In argument matching, It.IsAny<T> is used like a type declaration to fill the place of any concrete variable used as an argument. (src)

mock.Setup(x => x.IsValid(It.IsAny<string>())).Returns(true);
mock.Setup(x => x.IsValid("Hello, world!")).Returns(true);

The mock object exposes an Object property that can be used to test assertions against properties of the mocked object.

Assert.Equal(mock.Object.Property, value)

A mock object's Verify is used to verify that a mocked method was called by the system under test. Verification is specific to the parameters of the mocked method call, and argument matching is available just as it is for setting up mocked methods.

Here, the mocked validator, which is passed in to the SUT by dependency injection, must make a call to the validator's Evaluate() method. If the call is removed, the test will fail ("Expected invocation on the mock at least once, but was never performed...").

An overload of the Verify method also allows a custom error message to be specified. Another overload can ensure that the mocked method was not called, by passing Times.None after the lambda. The Times struct exposes other members like AtLeastOnce and Between that can specify any imaginable number or range of invocations.

public class StarshipDeploymentShould
{
    [Theory]
    [InlineData("Betelgeuse")]
    public void EvaluateStarship(string destination)
    {
        var mockValidator = new Mock<IStarshipValidator>();
        mockValidator.Setup(x => x.Evaluate()).Returns(true);

        var mockStarship = new Mock<IStarship>();

        var sut = new StarshipDeployment(mockValidator.Object as IStarshipValidator);
        sut.Deploy(mockStarship.Object as Starship, destination);
        mockValidator.Verify(x => x.Evaluate());
    }
}
public class StarshipDeploymentShould
{
    [Theory]
    [InlineData("Betelgeuse")]
    public void EvaluateStarship(string destination)
    {
        var mockValidator = new Mock<IStarshipValidator>();
        mockValidator.Setup(x => x.Evaluate()).Returns(true);

        var mockStarship = new Mock<IStarship>();

        var sut = new StarshipDeployment(mockValidator.Object as IStarshipValidator);
        sut.Deploy(mockStarship.Object as Starship, destination);
        mockValidator.Verify(x => x.Evaluate(), "Starships should be validated");
    }
}
public class StarshipDeployment
{
    public IStarshipValidator StarshipValidator { get; set; }

    public StarshipDeployment(IStarshipValidator validator)
    {
        StarshipValidator = validator ?? throw new ArgumentNullException(nameof(validator));
    }

    public bool ValidateDestination(string destination)
    {
        return destination.Length > 1 ? true : false;
    }

    public StarshipMission Deploy(Starship starship, string destination)
    {
        bool destinationValidated = ValidateDestination(destination);
        bool starshipValidated = StarshipValidator.Evaluate();

        return destinationValidated && starshipValidated
            ? new StarshipMission { Starship = starship as Starship, Destination = destination }
            : throw new ArgumentException();
    }
}

A mocked method can also be setup to throw an exception with the Throw<Exception>() method, a generic method that takes an Exception type.

Application design

Test-driven development and the requirement to be able to mock data providers has a strong influence on application architecture. Instead of tightly coupling models with a particular data provider (such as an hardcoding, an in-memory database, or parsing a file), the recommended pattern is dependency injection. A data provider that implements an interface is passed as an argument to the controller or viewmodel upon entry.

For example, a DataProvider class is used to provide a list of integers on application load implements

public interface IDataProvider
{
    IEnumerable<int> LoadAsync();
}

public class DataProvider : IDataProvider
{
    async public List<int> LoadAsync()
    {
        return await List<int> {1, 2, 3, 5, 7, 11, 13, 17, 19, 23, 29, 31};
    }
}

A mocked data provider also implementing that interface can then be used in testing.

.NET

The .NET ecosystem has 3 runtimes, all of which implement the .NET Standard Library and rest on common build tools, languages, and runtime components

  • .NET Framework released in 2002, making it the oldest runtime, and runs only on Windows. Two major components:
    • Common Language Runtime (CLR) runs managed code and performs garbage collection
    • .NET Framework Class Library (also called the Base Class Library) is composed of many classes, interfaces, and value types
  • .NET Core is cross-platform, open-source, and optimized for performance. Its application host is dotnet.exe
    • Core Common Language Runtime (CoreCLR) is more lightweight than that of .NET Framework, but implements Just-In Time compilation
    • .NET Core Class Library is smaller than (and actually a subset of) that of .NET Framework
  • Mono for Xamarin is used for mobile platforms like IOS, Android, and OS X

.NET Standard is a specification of which APIs are available across all these runtimes. It evolved from Portable Class Libraries (PCL) and will eventually replace them. .NET's package manager is NuGet.

An assembly can be compiled to EXE or DLL.

dotnet

Install dotnet-format

dotnet tool install -g dotnet-format
Install the ASP.NET scaffolding engine
dotnet tool install -g dotnet-aspnet-codegenerator
Create a new xUnit project named tests
dotnet new xunit -n tests
Add a project file to a solution
dotnet sln add ./Project/Project.csproj
Add a project reference to a project
dotnet add reference ./path/to/Project.csproj
Install a NuGet package and add a PackageReference in the project file

dotnet add package Moq
dotnet add package System.CommandLine

Run the dotnet try web server that supports .NET Interactive-style markdown:

    ```cs --source-file ./Program.cs --project ./project.csproj
    ```

Project files

Project files are XML files that describe various metadata to the dotnet compiler. The root node is Project which has two subnodes that collect various information about the project:

PropertyGroup can contain various elements that affect project settings:

  • RootNamespace specifies the namespace that contains the Main() method for console applications
  • TargetFramework specifies the targeted CLR framework: net5.0, netcoreapp3.1, etc
  • LangVersion C# version: 9.0, etc
  • Nullable Enable nullable reference types

ItemGroup contains references to NuGet packages (PackageReference) and other projects (ProjectReference).

<PropertyGroup>
    <LangVersion>preview</LangVersion>
</PropertyGroup>
<PropertyGroup>
    <Nullable>enable</Nullable>
</PropertyGroup>
<ItemGroup>
    <ProjectReference Include="/path/to/OtherProject.csproj"/>
    <PackageReference Include="xunit" Version="2.4.0"/>
</ItemGroup>

Adding a reference to another project is also easily accomplished from the command-line.

dotnet add project /path/to/OtherProject.csproj

Packages

NuGet is the official package manager for .NET.

NuGet packages required for any project were stored in a XML packages.config file.

But projects that use PackageReference may store that information in /obj/project.assets.json.

System.CommandLine

Prior to System.CommandLine, it had been up to the developer to build a custom solution resolving command-line arguments as an array of strings. Although .NET includes several earlier attempts at solving this problem, none had emerged as a default solution.

Similar to Python's argparse , the CommandLine library allows you to construct a RootCommand object that accepts definitions of argument and options.

Here, an argument is required:

using System;
using System.CommandLine;
using System.CommandLine.Invocation;

namespace CommandLine
{
    class Program
    {
        static int Main(string[] args)
        {
            var cmd = new RootCommand
            {
                new Argument<string>("name")
            };

            cmd.Handler = CommandHandler.Create<string>(HandleGreeting);

            return cmd.Invoke(args);
        }

        static void HandleGreeting(string name = "world")
        {
            Console.WriteLine($"Hello, {name}");
        }
    }
}
import argparse


def get_args():
    parser = argparse.ArgumentParser(description="Say hello")
    parser.add_argument(
        dest="name",metavar="name", default="World", help="Name to greet"
    )
    return parser.parse_args()


def main():
    args = get_args()
    print(f"Hello, {args.name}!")


if __name__ == "__main__":
    main()

Here, the greeting can be specified with an optional parameter

using System;
using System.CommandLine;
using System.CommandLine.Invocation;

namespace CommandLine
{
    class Program
    {
        static int Main(string[] args)
        {
            var cmd = new RootCommand
            {
                new Argument<string>("name"),//, "Your name"),
                new Option<string?>(new[] {"--greeting", "-g" },"The greeting to use"),
            };

            cmd.Handler = CommandHandler.Create<string, string?>(HandleGreeting);

            return cmd.Invoke(args);
        }

        static void HandleGreeting(string? greeting, string name)
        {
            Console.WriteLine($"{greeting}, {name}");
        }
    }
}
import argparse


def get_args():
    parser = argparse.ArgumentParser(description="Say hello")
    parser.add_argument(
        dest="name",metavar="name", default="World", help="Name to greet"
    )
    parser.add_argument(
        "--greeting","-g", dest="greeting", default="Hello", help="Greeting to use"
    )
    return parser.parse_args()


def main():
    args = get_args()
    print(f"{args.greeting}, {args.name}!")


if __name__ == "__main__":
    main()

Documentation

C# supports documentation comments that can be exported to an XML file, which can then be imported into a static site generator (especially DocFX).

Visual Studio can be set to export these comments upon build.

SDKs

DynamoDB

To develop a .NET application using DynamoDB, add the AWSSDK.DynamoDBv2 NuGet package. The AWS Explorer, part of the AWS Toolkit for Visual Studio extension, is also useful for setting up a new table. A user with programmatic access, including an Access Key and Secret Key, is necessary to use the toolkit. (src)

Both .NET Core and .NET Framework are supported as target frameworks, but .NET Core uses exclusively asynchronous operations.

A service client object is formed by instantiating AmazonDynamoDBClient.

The exposed method PutItemAsync is used to save an item to a table as a PutItemRequest object. The item itself is provided as a Dictionary in the Item key, but the Dictionary's values are AttributeValue objects, formed with a magic key that determines the data type of the value.

new AttributeValue{ S = "Hello, world!" }
new AttributeValue{ N = "3" }
new AttributeValue{ BOOL = true }
new AttributeValue{ L = new List<AttributeValue>
{
    new AttributeValue{ S = "Socrates" },
    new AttributeValue{ S = "Plato" },
    new AttributeValue{ S = "Aristotle" },
}}
using Amazon.DynamoDBv2;

namespace DynamoDBDemo
{
    public class LowLevelSample
    {
        public static async Task ExecuteAsync()
        {
            using (IAmazonDynamoDB ddbClient = new AmazonDynamoDBClient()
            {
                await ddbClient.PutItemAsync(new PutItemRequest
                {
                    TableName = "Users",
                    Item = new Dictionary<string, AttributeValue>
                    {
                        { "Id", new AttributeValue { S = "john@doe.com" } },
                        { "String", new AttributeValue { /* ... */ } }
                    }
                })
            })
        }
    }
}

Concurrency

Asynchronous programming

Consuming APIs:

  • HttpClient

Multithreading

The Task Parallel Library offers a high-level way to set up multiple threads.

A Task represents an asynchronous operation.

Task.Run() queues the work passed as the action to run on a different thread in the thread pool. Task.Run<T>() represents an asynchronous operation that returns a specific value type.

Task.Run( () => 
{
    // ...
});

Objects in other threads will be inaccessible without using an object like Dispatcher in WPF

Task.Run( () =>
{
    Dispatcher.Invoke(() => 
    {
        // ...
    });
});

To avoid blocking, we can make it asynchronous

private async void Search_Click(object sender, RoutedEventArgs e)
{
    await Task.Run() =>
    {
        // ...

        Dispatcher.Invoke(() =>
        {
            // ...
        }
    }
}

📘 Glossary

Assembly
A collection of types and resources that are built to work together and form a logical unit of functionality and which form the building blocks of .NET applications.
Module
A portable executable file (DLL or EXE) consisting of one or more classes and interfaces. Although multiple modules can theoretically compose a single assembly, in practice an assembly and module can be considered one and the same for most .NET applications.
Provider pattern
A favored development model in .NET, and a form of dependency injection where a class is passed as an argument to another class that uses it for some purpose. The key is that the provider must derive from an abstract base class or an interface to support mocks in unit testing.