You cannot select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
6.9 KiB
6.9 KiB
marp | paginate | math | theme | title |
---|---|---|---|---|
true | true | mathjax | buutti | 15. Design Patterns in C# |
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
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, 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(); }