Design Patterns in C#

Overview

  • The Singleton Pattern
  • Reflection and attributes
  • Dependency Injection

The Singleton Pattern

The problem

  • In most cases, it makes no sense to create an instance of a class every time its members need to be accessed
    • For example, a shared resource manager that is being called from multiple classes
  • While a static class could be used for this, there are some problems:
    • As stated in lecture 10, static classes can only have static members
    • Static classes cannot be instantiated, so a reference to them cannot be passed around as a parameter
    • Static classes cannot inherit from other classes or implement interfaces
    • And many more...

The solution

  • The singleton class is a class that benefits from all the perks of a non-static class (non-static members, inheritance, referencing…), but only one (or zero) instances of it ever exists during the lifetime of your application
  • For example, reading from / writing to a file that should be accessible to multiple clients, should be made into a singleton
    • Instead of every client directly accessing the same file (and possibly causing massive performance issues), the singleton is instantiated once and a reference to it is provided to clients
    • The singleton could take care of queueing the read/write requests and be the only entity accessing the actual file

A singleton implementation could look something like this:

class Singleton
{
  private static Singleton instance = null;

  private Singleton() { }
  public void MySingletonFunction()
  {
    Console.WriteLine
      ("This function is accessible anywhere!");
  }

  public static Singleton Instance
  {
    get
    {
      if (instance == null)
        instance = new Singleton();
      return instance;
    }
  }
}
class Program
{
  static void Main(string[] args)
  {
    Singleton.Instance.MySingletonFunction();
    // Outputs: "This function is accessible
    // from everywhere!"
  }
}

Implementing a singleton pattern

  • The exact implementation of the singleton is out of the scope of this course, but it is important to understand that it exists and what its purpose is
  • Multitude of examples for different use cases are available and can be found by googling

Reflection

  • Reflective programming or reflection is the ability for the program to examine or modify its own structure and behaviour
  • C# has special reflection methods that we can use to get information about types
  • Simple reflection example to obtain type information:
    int i = 42;
    Type type = i.GetType();
    Console.WriteLine(type);
    
  • Use strings to invoke methods:
    // Without reflection
    var foo = new Foo();
    foo.PrintHello();
    
    // With reflection
    Object foo = Activator.CreateInstance
      ("complete.classpath.and.Foo");
    MethodInfo method = foo
      .GetType()
      .GetMethod("PrintHello");
    method.Invoke(foo, null);
    

Attributes

  • Attributes can be used to extend methods, classes, or even entire programs with new metadata
    • Metadata is information about the types defined
  • Attributes use reflection so the program can examine its own metadata
  • Here's an example of the built-in SerializableAttribute.
  • Attribute is used by placing its name in square brackets [] above the declaration of the entity you want it to apply to
    [Serializable]
    public class SampleClass
    {
        // Objects of this type can be serialized.
    }
    
  • Attributes can also have parameters:

    [Obsolete("Will be removed in next version.")]
    public static int Add(int a, int b)
    {
        return (a + b);
    }
    
  • You can add multiple attributes either on separate lines or by separating them with a comma:

[Conditional("DEBUG")]
[Conditional("TEST1")]
void TraceMethod()
{
    // ...
}
[Conditional("DEBUG"), Conditional("TEST1")]
void TraceMethod()
{
    // ...
}

Custom attributes

  • You can write your own custom attributes by inheriting from the Attribute class
    // This defaults to Inherited = true.
    public class MyAttribute : Attribute
    {
        //...
    }
    
  • Then, you can use it like any other attribute
    public class MyClass
    {
        [MyAttribute]
        public virtual void MyMethod()
        {
            //...
        }
    }
    

Dependency Injection

The problem

  • Traditionally, when new objects of classes are instantiated, the consuming class handles the creation of the objects
  • Many classes change their functionality throughout the development of any project
    • This means that also every single consuming class has to change
    • This is called tight coupling
  • What we want instead is loose coupling, where components have little or no knowledge about separate components' definitions, and a change in one component doesn't necessitate a change in another

The solution

  • What if, instead of directly creating the objects, they were provided by some interface that takes care of the creation?
  • This way, even if the base class changes, the consuming classes won't care because they only know about the provider
  • This provider is called Container, and the functionality being injected is called Service
  • In ASP.NET (Microsoft's framework for building web applications), this container system is built in

Dependency injection in ASP.NET

public class HomeController : Controller
{
  private readonly IUserRepository _userRepository;

  public HomeController(IUserRepository userRepository)
  {
    _userRepository = userRepository;
  }
  // User repository including all users is now accessible in HomeController
}

Design Patterns

  • If the concepts of a singleton and dependency injection flew over your head, don't worry about it
  • The important thing is to know they exist so that when they come up again in ASP.NET, you have already familiarized yourself with the terms
    • Thus, understanding the logic behind ASP.NET becomes less overwhelming
  • There are many more design patterns, see the material here
    public void ConfigureServices(IServiceCollection services)
    {
      services.AddSingleton<IDateTime, SystemDateTime>();
      services.AddControllersWithViews();
    }