Advanced Topics in C#

Some person notes on Advanced Topics programming with C#.

 

Inheritance vs Composition

Inheritance and composition isnt specific to C# but to applies to any Object Oriented Programming language. The Inheritance model allows for “IS-A” relationships. For example, we can have a parent Fruit class and a child Apple class. The child Apple class “IS-A” fruit and therefore contains the same properties (or methods) as it’s parent. In C#, we can do this through class extensions such as using Interfaces or Abstract classes.

    class Fruit
    {
        public string color { get; set; }
    }

    class Apple : Fruit
    {
        public Apple()
        {
            this.color = "Red";
        }
    }

The Composition model is different in that it defines a “HAS-A” relationship. In the same Apple example, we can define a new class called Seed and the Apple class can instantiate the Seed class. In this case, the Apple class “HAS-A” Seed class. In C# these relationships are defined using the “new” keyword when instantiating the relationship.

    class Apple : Fruit
    {
        public Seed Seeds;

        public Apple()
        {
            this.color = "Red";
            this.Seeds = new Seed(10);
        }
    }

    class Seed
    {
        public int count { get; set; }

        public Seed(int count)
        {
            this.count = count;
        }
    }

 

Interface vs Virtual vs Abstract

In C# an Interface is automatically considered abstract. As stated here:

The CLR requires that interface methods be marked as virtual. If you do not explicitly mark the method as virtual in your source code, the compiler marks the method as virtual and sealed; this prevents a derived class from overriding the interface method. If you explicitly mark the method as virtual, the compiler marks the method as virtual (and leaves it unsealed); this allows a derived class to override the interface method. If an interface method is sealed, a derived class cannot override the method. However, a derived class can re-inherit the same interface and can provide its own implementation for the interface’s methods.

In C#, a derived (child) class can only inherit from one abstract class. When it does, it can override it’s parents methods or reuse them. Therefore an abstract class can contain implementation, though not mandatory. If there are any abstract methods, the class must be defined as abstract. It’s derived classes must override the abstract methods and that child class cannot be abstract itself.

An interface on the other hand requires the derived class to implement all the methods. There is nothing to override because interfaces have no implementations.

Virtual methods can also be overrridden by it’s derived class, much like abstract methods. However, abstract methods do not need to have an implementation, virtual methods must have an implementation. Since virtual methods can have implementation, the derived class may not need to override them (they must for abstract methods).

    public abstract class Message
    {
        public abstract void Send();
    }

    public abstract class Mail
    {
        public abstract void Send();
    }

    public class Fax
    {
        public void Send()
        {
            throw new NotImplementedException();
        }
    }

    interface IMessage
    {
        void Content();
    }

    interface IRoute
    {
        void Path();
    }

    public class Sender : Message, IMessage, IRoute
    {
        public void Content()
        {
            throw new NotImplementedException();
        }

        public void Path()
        {
            throw new NotImplementedException();
        }

        public override void Send()
        {
            throw new NotImplementedException();
        }
    }

 

Sealed

Sealed properties cannot be overridden in the derived classes. It is used to stop inheritenance. In traditional VB.NET this was done with the keyword “NotInheritable”. In C# we have the “sealed” keyword.

sealed class Dog
{
    public void Size()
    {
        throw new NotImplementedException();
    }
}

//class Minpin : Dog { } // Error - Cannot be derived from sealed class

abstract class Animal
{
    public abstract void Size();
}

class Fish : Animal
{
    public sealed override void Size()
    {
        throw new NotImplementedException();
    }

    void Swims()
    {
        throw new NotImplementedException();
    }
}

 

Singletons

Singleton is a class which can have only one instance created, is globally accessible and does not take any parameters when instantiating. The  Some common use cases for singletons are for intensive service calls, such as to a database, api or even file I/O such as logging.

public sealed class SiteStructure
{
    static readonly SiteStructure _instance = new SiteStructure();
    public static SiteStructure Instance
    {
        get
        {
            return _instance;
        }
    }
    SiteStructure()
    {
        // Initialize.
    }
}

 

Singleton vs Static

Singleton is considered stateful whereas static is not. In other words, Singletons can be instantiated at certain states, they can also extend other classes or implement interfaces. Static cannot. Static are more for generic functions or properties that do not change state over the life of the application.

The other difference is that singletons must be implemented where static have keywords the compiler can identify. Because singletons must be implemented, it can have slightly different behaviors (such as lazy instantiation).

 

Parallel Tasking

In C# a “Task” represents an asynchronous operation. The task code operates asynchronously on a thread pool that is separate from the main application thread. This can be seen in Visual Studio by the “Status” property during debug. Some of the items a Task returns are “IsCanceled, IsCompleted and IsFaulted”. Tasks are used as part of the task-based asynchronous pattern (TAP).

TAP – Task-based Asynchronous Pattern

The Task-based Asynchronous Pattern (TAP) is based on the System.Threading.Tasks.Task and System.Threading.Tasks.Task<TResult> types in the System.Threading.Tasks namespace, which are used to represent arbitrary asynchronous operations. TAP uses a single method to represent the initiation and completion of an asynchronous operation. This contrasts with both the Asynchronous Programming Model (APM or IAsyncResult) pattern and the Event-based Asynchronous Pattern (EAP). APM requires Begin and End methods. EAP requires a method that has the Async suffix and also requires one or more events, event handler delegate types, and EventArg-derived types. Asynchronous methods in TAP include the Async suffix after the operation name for methods that return awaitable types, such as TaskTask<TResult>ValueTask, and ValueTask<TResult>.

using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Threading.Tasks;

namespace asyncdemo
{
    class Program
    {
        static async Task Main(string[] args)
        {
            Console.WriteLine("Hello World!");

            Stopwatch watch = new Stopwatch();
            watch.Start();

            //Console.WriteLine("Example1 - no wait");
            //RunExample1(watch);
            /*
            RunAsync10s Start!: 00:00:00.0009318
            RunAsync7s Start!: 00:00:00.0031089
            RunAsync3s Start!: 00:00:00.0038067
            ALL DONE! 00:00:00.0038817
            RunAsync3s Done!: 00:00:03.0044667
            RunAsync7s Done!: 00:00:07.0040369
            RunAsync10s Done!: 00:00:10.0017448
            Final: 00:00:12.2381675
            */

            //Console.WriteLine("Example2 - wait, called with no wait");
            //RunExample2(watch);
            /*
            RunAsync10s Start!: 00:00:00.0013178
            ALL DONE! 00:00:00.0029442
            RunAsync10s Done!: 00:00:10.0029396
            RunAsync7s Start!: 00:00:10.0048529
            RunAsync7s Done!: 00:00:17.0063805
            RunAsync3s Start!: 00:00:17.0082551
            RunAsync3s Done!: 00:00:20.0091404
            Final: 00:00:22.9761675
            */

            //Console.WriteLine("Example2 - wait, called with wait");
            //await RunExample2(watch);
            /*
            RunAsync10s Start!: 00:00:00.0013146
            RunAsync10s Done!: 00:00:10.0021543
            RunAsync7s Start!: 00:00:10.0041034
            RunAsync7s Done!: 00:00:17.0055400
            RunAsync3s Start!: 00:00:17.0073895
            RunAsync3s Done!: 00:00:20.0084613
            ALL DONE! 00:00:20.0085784
            Final: 00:00:22.5927585
            */

            //Console.WriteLine("Example3 - WhenAll");
            //await RunExample3(watch);
            /*
            RunAsync10s Start!: 00:00:00.0018251
            RunAsync7s Start!: 00:00:00.0034897
            RunAsync3s Start!: 00:00:00.0040749
            RunAsync3s Done!: 00:00:03.0068816
            RunAsync7s Done!: 00:00:07.0077645
            RunAsync10s Done!: 00:00:10.0025321
            Completed RunAsync10
            Completed RunAsync7
            Completed RunAsync3
            ALL DONE! 00:00:10.0027409
            Final: 00:00:11.1752193
            */

            Console.WriteLine("Example4 - Result");
            RunExample4(watch);
            /*
            RunAsync10s Start!: 00:00:00.0008287
            RunAsync10s Done!: 00:00:10.0020883
            RunAsync7s Start!: 00:00:10.0039718
            RunAsync7s Done!: 00:00:17.0081551
            RunAsync3s Start!: 00:00:17.0096466
            RunAsync3s Done!: 00:00:20.0105567
            ALL DONE! 00:00:20.0106627
            Final: 00:00:21.9910794            
            */

            Console.WriteLine($"ALL DONE! {watch.Elapsed}");
            Console.Read();
            watch.Stop();
            Console.WriteLine($"Final: {watch.Elapsed}");
        }


        public static void RunExample1(Stopwatch watch)
        {
            RunAsync10s(watch);

            RunAsync7s(watch);

            RunAsync3s(watch);
        }

        public static async Task RunExample2(Stopwatch watch)
        {
            await RunAsync10s(watch);

            await RunAsync7s(watch);

            await RunAsync3s(watch);
        }

        public static async Task RunExample3(Stopwatch watch)
        {
            List<Task> tasks = new List<Task>();
            tasks.Add(RunAsync10s(watch));
            tasks.Add(RunAsync7s(watch));
            tasks.Add(RunAsync3s(watch));

            var results = await Task.WhenAll(tasks);

            foreach(var result in results)
            {
                Console.WriteLine($"Completed RunAsync{result.ToString()}");
            }
        }

        public static void RunExample4(Stopwatch watch)
        {
            var a = RunAsync10s(watch).Result;

            var b = RunAsync7s(watch).Result;

            var c = RunAsync3s(watch).Result;
        }

        public static async Task RunAsync10s(Stopwatch watch)
        {
            Console.WriteLine($"RunAsync10s Start!: {watch.Elapsed}");
            await Task.Delay(10000);
            Console.WriteLine($"RunAsync10s Done!: {watch.Elapsed}");
            return 10;
        }
        public static async Task RunAsync7s(Stopwatch watch)
        {
            Console.WriteLine($"RunAsync7s Start!: {watch.Elapsed}");
            await Task.Delay(7000);
            Console.WriteLine($"RunAsync7s Done!: {watch.Elapsed}");
            return 7;
        }
        public static async Task RunAsync3s(Stopwatch watch)
        {
            Console.WriteLine($"RunAsync3s Start!: {watch.Elapsed}");
            await Task.Delay(3000);
            Console.WriteLine($"RunAsync3s Done!: {watch.Elapsed}");
            return 3;
        }
    }
}

Exception Handling

When working with multiple tasks in parallel, we need to handle exceptions for the whole set. For example, when using the Task.WaitAll() – the code will still execute all tasks regardless of which ones error out. For those tasks that error, it will be propageted up into the AggregateException object. See example below.

using System;
using System.Threading.Tasks;

public class Example
{
   public static void Main()
   {
      var task1 = Task.Run( () => { throw new CustomException("This exception is expected!"); } );

      try
      {
          task1.Wait();
      }
      catch (AggregateException ae)
      {
          foreach (var e in ae.InnerExceptions) {
              // Handle the custom exception.
              if (e is CustomException) {
                  Console.WriteLine(e.Message);
              }
              // Rethrow any other exception.
              else {
                  throw;
              }
          }
      }
   }
}

public class CustomException : Exception
{
   public CustomException(String message) : base(message)
   {}
}

 

 

Extension Methods

Extension methods enable you to “add” methods to existing types without creating a new derived type, recompiling, or otherwise modifying the original type. Extension methods are a special kind of static method, but they are called as if they were instance methods on the extended type. Extension methods are still part of the Open Closed Principle since they are allow extensibility of classes.

An example of extension methods is the “.count()” method. It can be called from any System.Linq, System.Collections, and event System.String. These are not in a shared hierarchy, therefore not part of Interfaces or Abstract classes. The “Count()” example is shown below. Note that it works against the string<list> only by importing the System.Linq library.

using System;
using System.Collections.Generic;
using System.Linq;

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

            var s = new List { "a", "b", "c" };

            var c = s.Count();

            Console.WriteLine($"The count is {c}");
        }
    }
}

To write Extension Methods, we create a Sponsor Class which defines to which namespaces the method will be available at. These are static utility classes.

using System;
namespace ExtensionMethods
{
    public static class Extensions
    {
        public static bool IsLengthEven(this String str)
        {
            return false;
        }
    }
}


using System;
using System.Collections.Generic;
using System.Linq;
using ExtensionMethods;
namespace DnetExtensions
{
    class Program
    {
        static void Main(string[] args)
        {
            Console.WriteLine("Example of Extension Metods");

            var e1 = new List { "a", "b", "c" };

            var r1 = e1.Count(); // .net built in ext method from System.Linq

            Console.WriteLine($"R1 = {r1}");

            var e2 = "Hello World";

            var r2 = e2.IsLengthEven(); // custom ext method

            Console.WriteLine($"R2 = {r2}");
        }
    }
}

 

Note that the sponsor class’s namespace determines how the extension method is used. Sometimes, we can reuse the namespace name of the class you are extending. So in the example below we’re using the System namespace so we don’t need to have the “using ExtensionMethods” declaration.

using System;
namespace System
{
    public static class Extensions
    {
        public static bool IsLengthEven(this String str)
        {
            return false;
        }
    }
}

 

Events, Delegates and Event Handlers

Events are notifications that are triggered. For example, in WinForms the button click would be a “ClickEvent”. Events can have arguments attached with them, or EventArgs. Delegates are function pointers – it is the pipeline that allows an event with it’s eventArgs get to the EventHandler function, hence a pointer. The EventHandler usually receives the Sender and EventArgs, again passed in via the Delegate.

Example below shows how a delegate can be instantiated. There are 3 delegates where Delegate3 is going to invoke, this is called an Invocation List.

using System;

namespace DnetDelegates
{
    class Program
    {
        public delegate void WorkPerformedHandler(int hours); // custom delegate

        static void Main(string[] args)
        {
            Console.WriteLine("Delegates and Lambdas");

            WorkPerformedHandler delegate1 = new WorkPerformedHandler(WorkPerformed1);
            WorkPerformedHandler delegate2 = new WorkPerformedHandler(WorkPerformed2);
            WorkPerformedHandler delegate3 = new WorkPerformedHandler(WorkPerformed3);

            delegate3 += delegate1 + delegate2; // Invocation List
            delegate3(15); // Will call all 3 delegates
        }

        static void WorkPerformed1(int hours)
        {
            Console.WriteLine($"Worked Performed 1 for {hours}");
        }
        static void WorkPerformed2(int hours)
        {
            Console.WriteLine($"Worked Performed 2 for {hours}");
        }
        static void WorkPerformed3(int hours)
        {
            Console.WriteLine($"Worked Performed 3 for {hours}");
        }
    }
}

 

Below is an example of a corresponding event and EventArgs. Note that it is using the System EventHandler<t> as declaration.

// The Event
public class Worker
{
    public event EventHandler WorkPerformed;
    public event EventHandler WorkCompleted; // .nets built in EventHandler

    public void DoWork(int hours)
    {
        OnWorkPerformed(hours);
        OnWorkCompleted();
    }

    protected virtual void OnWorkPerformed(int hours)
    {
        var wp = WorkPerformed as EventHandler;
        if (wp != null)
        {
            wp(this, new WorkPerformedEventArgs(hours));
        }
    }

    protected virtual void OnWorkCompleted()
    {
        var wc = WorkCompleted as EventHandler;
        if (wc != null)
        {
            wc(this, EventArgs.Empty);
        }
    }
}

// The EventArgs
public class WorkPerformedEventArgs : EventArgs
{
    public WorkPerformedEventArgs(int hours)
    {
        Hours = hours;
    }

    public int Hours { get; set; }
}

 

The last example shows the handlers which processes the events shown above.

static void Main(string[] args)
{
    Console.WriteLine("Delegates and Lambdas");

    var worker = new Worker();
    worker.WorkPerformed += new EventHandler(WPHandler);
    worker.WorkCompleted += new EventHandler(WCHandler);
    worker.DoWork(8);
    //Hours WorkPerformed 8
    //Work Completed
}

static void WPHandler(object sender, WorkPerformedEventArgs e)
{
    Console.WriteLine($"Hours WorkPerformed {e.Hours}");
}
static void WCHandler(object sender, EventArgs e)
{
    Console.WriteLine($"Work Completed");
}

 

Delegate Inference

As shown above, the “+=” operator is used to attach an event to an event handler. This can be changed and use delegate inference. IT looks like the following:

var worker = new Worker();
worker.WorkPerformed += WPHandler;
worker.WorkCompleted += WCHandler;
worker.DoWork(8);

 

Anonymous Functions

Ability to hook code directly to an event. Methods use the “delegate” keyword when using anonymous methods. As shown below.

worker.WorkPerformed += delegate (object sender, WorkPerformedEventArgs e) {
    Console.WriteLine($"Hours WorkPerformed {e.Hours}");
};

 

 

Lambdas

Works very well with Delegates. Lambda is an anonymous function. It includes lambda parameters, which dont need types because the compiler will infer it. The lambda parameters could also be empty. Next is the lambda operator with the method body.

Taking the examples from the Delegates section above, we can remake the delegate Event calls using lambdas as follows:

var worker2 = new Worker();
worker2.WorkPerformed += (s, e) =>
{
    Console.WriteLine("Work Performed " + e.Hours);
};
worker2.WorkCompleted += (s, e) => Console.WriteLine("Work Completed");
worker2.DoWork(5);

 

A more complete example is shown below where we can dynamically define a process function that can take 2 integers but the definition is dynamic – using lambda functions. Each of the functions can be defined using a delegate or an action or a func.

using System;
namespace DnetLambda
{
    public delegate int ProcessDelegate(int x, int y);

    class Program
    {
        static void Main(string[] args)
        {
            Console.WriteLine("Lambda Demonstration");

            // Delegates
            ProcessDelegate addDelegate = (x, y) => x + y;
            ProcessDelegate multDelegate = (x, y) => x * y;
            var process1 = new Process();
            process1.Delegate(3, 2, addDelegate);
            process1.Delegate(3, 2, multDelegate);

            // Actions
            Action<int, int> addAction = (x, y) => Console.WriteLine(x + y);
            Action<int, int> multiplyAction = (x, y) => Console.WriteLine(x * y);
            var process2 = new Process();
            process2.Action(3, 2, addAction);
            process2.Action(3, 2, multiplyAction);

            // Funcs
            Func<int, int, int> addFunc = (x, y) => x + y;
            Func<int, int, int> multFunc = (x, y) => x * y;
            var process3 = new Process();
            process3.Func(3,2,addFunc);
            process3.Func(3,2,multFunc);
        }
    }

    class Process
    {
        public void Delegate(int x, int y, ProcessDelegate d)
        {
            var result = d(x: x, y: y);
            Console.WriteLine($"Delegate: {result}");
        }

        public void Action(int x, int y, Action<int, int> action)
        {
            action(x, y);
            Console.WriteLine($"Action Completed");
        }

        public void Func(int x, int y, Func<int, int, int> f)
        {
            var result = f(x,y);
            Console.WriteLine($"Func: {result}");
        }
    }
}

 

 

 

 

 

 

 

 

 

 


References

Extension Methods
https://docs.microsoft.com/en-us/dotnet/csharp/programming-guide/classes-and-structs/extension-methods

 

 

eof