“This is the fourth day of my participation in the Gwen Challenge in November. Check out the details: The Last Gwen Challenge in 2021.”

A, the IOC

1. What is IOC?

Inversion of control Inversion of Control, abbreviated as IoC), is one of the [] object-oriented programming design principle, can be used to reduce the computer code/coupling between one of the most common way is called Dependency Injection (Dependency Injection, referred to as “DI”), Another approach is called Dependency Lookup.

IoC: It is a design pattern

DI: It's a way of practicing the idea of inversion of control

2.为什么要用IOC

Because IoC inversion of control relies on abstractions, which are stable and do not depend on details, which may also depend on other details, dependency injection is used to address infinite levels of object dependencies in order to mask details.

IoC containers commonly used in 3.Net

Currently the most used are AutoFac and Castle, in. IOC container is built into the framework of Net Core. Unity and ObjectBuilder are relatively old frameworks, which are seldom used.

  1. AutoFac

  2. Castle

  3. Unity

  4. ObjectBuilder

Two, how to handwriting?

1. Basic design

Core idea: Factory + reflection

First of all, it is not difficult for us to implement an IoC container by ourselves. We all know that we need to register before using the existing IoC container

Therefore, we changed the factory to manual registration, because it is not very beautiful to write a lot of if else or switch. According to the use of mainstream IoC, we will follow the same pattern. If we continue to improve the function and add assembly injection later, we still have to implement a factory to omit manual registration

However, the goal this time is to achieve a simple IoC container. We will implement the basic functions first, and then we will improve them step by step, and then we will add some new functions. That is, we do not consider performance or scalability, but step by step

  1. To facilitate access and extension, we define a container interface called IManualContainer

  2. Define the ManualContainer inheritance implementation IManualContainer

  3. Declares a static dictionary object to store registered objects

  4. Using reflection to build objects, you can add Expression or Emit to optimize for performance

classDiagram
IManualContainer <|-- ManualContainer
IManualContainer: +Register<TFrom, To>()
IManualContainer: +Resolve<Tinterface>()
class ManualContainer{
-Dictionary<string, Type> container
+Register()
+Resolve()
-CreateInstance()
}
public interface IManualContainer
{
    void Register<TFrom, To>() where To : TFrom;

    TFrom Resolve<TFrom>();
}
Copy the code
2. Functions to be implemented

1. Basic object construction

Constructor injection

3. Multilevel dependencies and constructors and custom injection

4. Property injection & method injection

5. Single interface with multiple implementations

6. Construct the incoming instance type

Three, coding implementation and thinking analysis

1. Implementation of the construction object (single interface injection)

1. Implement the interface to encode private fields. Container stores the registered type.

2. The generic constraint guarantees that it needs To be implemented by Resolve (To) or inherited from the registered type (TFrom).

public class ManualContainer : IManualContainer {private static Dictionary<string, Type> container = new Dictionary<string, Type>(); Public void Register<TFrom, To>() where To: TFrom {string Key = typeof(TFrom).fullName; if (! container.ContainsKey(Key)) { container.Add(Key, typeof(To)); }}Copy the code

1. To implement the constructed object, we first need to pass in the abstract interface T of the constructed type

2. In Resolve, locate the type mapped at registration time in the storage container based on T as the Key, and construct the object by reflection

Public TFrom Resolve<TFrom>() {string key = typeof(TFrom).fullname; container.TryGetValue(key, out Type target); if(target is null) { return default(TFrom); } object t = Activator.CreateInstance(target); }}Copy the code

1. First we prepare the interfaces (ITestA) and instances (TestA) needed to construct objects using containers

public interface ITestA { void Run(); } public class TestA: ITestA {public void Run()=> Console.WriteLine(" this is the implementation of the ITestA "); }Copy the code

2. Call the IoC container to create the object

IManualContainer container = new ManualContainer(); Container.Register<ITestA, TestA>(); ITestA instance = container.Resolve<ITestA>(); instance.Run(); //out put "This is an implementation of ITestA"Copy the code
Constructor injection

1. Suppose we need an instance of the ITestB interface or some other type of instance in our TestA class and need constructor injection, how can we improve our IoC container?

public class TestA : ITestA { private ITestB testB = null; Public TestA(ITestB testB)=> this.testB = testB; public void Run() { this.testB.Run(); Console.WriteLine(" This is an implementation of ITestA "); }}Copy the code

TestA (Resolve, Resolve, echo, echo, echo, echo, echo, echo, echo, echo, echo, echo, echo, echo, echo, echo, echo, echo, echo, echo, echo, echo, echo)

  1. Start by defining the List<object> collection to store the List of parameters required for object construction

  2. Find the constructor in the class by the target type that needs to be instantiated, regardless of the multi-constructor case

  3. Find the constructor parameters and type, then create an instance of the parameters to add to the List, passing in the parameters during reflection construction

Public TFrom Resolve<TFrom>() {string key = typeof(TFrom).fullname; container.TryGetValue(key, out Type target); if(target is null) { return default(TFrom); } // Store parameter List List<object> paramList = new List<object>(); Case var ctor = target.getconstructors ().firstorDefault (); Var ctorParams = ctor.getParameters (); Foreach (var item in ctorParams) {// ParameterType paramType = item.parametertype; string paramKey = paramType.FullName; Container.TryGetValue(paramKey, out Type ParamType); Add(activator.createInstance (ParamType)); } object t = Activator.CreateInstance(target,paramList); }}Copy the code
3. Multi-level dependencies (recursion)

Based on the results of our implementation so far, this solved the problem of constructors and multi-parameter injection as well as the basic problem of constructing objects, and now the problem arises again

  1. What if there are many layers of dependencies?

  2. What about multiple constructors, for example?

  3. What if the user wants to customize the constructor that needs to be injected from multiple constructors?

Sum up three questions

  • 1. Multi-level dependency problem

For example, an instance of ITestB that depends on ITestC, always depends on ITestC indefinitely how do we solve that? No doubt, to do the same thing, but to do it indefinitely, use recursion, and let’s modify our method

  • Multiple constructors

    1. Injection with the most parameters (AutoFac)

    2. Fetch and set injection (ASP.net Core)

  • 3. Customize injection

    This can be done using property tags, which the user adds to the constructor that needs to be selectively injected


1. Create a private recursive method that creates objects

private object CreateInstance(Type type)
{
}
Copy the code

2. We choose the first way to implement, modify the previous code to get the first constructor, select maximum parameter injection

ConstructorInfo ctor = null; ConstructorInfo ctor = null; var ctors = target.GetConstructors(); ctor = ctors.OrderByDescending(x => x.GetParameters().Length).First();Copy the code

3. Customize ManualCtorInjection, which allows users to customize the injection

/ / a custom Constructor injection mark [AttributeUsage (AttributeTargets. Constructor)] public class ManualCtorInjectionAttribute: Attribute {}Copy the code

4. Final code

private object CreateInstance(Type type) { string key = type.FullName; container.TryGetValue(key, out Type target); List<object> paramList = new List<object>(); ConstructorInfo ctor = null; Ctor = target.getConstructors ().firstorDefault (x => X. isdefined (typeof(ManualInjectionAttribute)), true)); // If not marked by a feature, If (ctor is null) {ctor = target.gettors ().OrderByDescending(x =>); x.GetParameters().Length).First(); Var ctorParams = ctor.getParameters (); Foreach (var item in ctorParams) {// ParameterType paramType = item.parametertype; Object paramInstance = CreateInstance(paramType); // Construct the instance and Add the parameter list paramlist.add (paramInstance); } object t = Activator.CreateInstance(target); return t; } public TFrom Resolve<TFrom>() { return (TFrom)this.CreateInstance(typeof(TFrom)); }Copy the code
4. Property injection & method injection

1. Customize ManualPropInjection, which allows users to customize the injection

/ / custom tectonic attribute injection mark [AttributeUsage (AttributeTargets. Constructor)] public class ManualPropInjectionAttribute: Attribute {} / / custom constructor injection mark [AttributeUsage (AttributeTargets. Method)] public class ManualMethodInjectionAttribute: Attribute { }Copy the code

2. Iterate over the attributes of the marked feature in the instance.

3. Get the type of the attribute and call the recursive constructor object function.

4. Set the properties of the target object.

5. The same is true for method injection

private object CreateInstance(Type type) { string key = type.FullName; container.TryGetValue(key, out Type target); List<object> paramList = new List<object>(); ConstructorInfo ctor = null; Ctor = target.getConstructors ().firstorDefault (x => X. isdefined (typeof(ManualInjectionAttribute)), true)); // If not marked by a feature, If (ctor is null) {ctor = target.gettors ().OrderByDescending(x =>); x.GetParameters().Length).First(); Var ctorParams = ctor.getParameters (); Foreach (var item in ctorParams) {// ParameterType paramType = item.parametertype; Object paramInstance = CreateInstance(paramType); // Construct the instance and Add the parameter list paramlist.add (paramInstance); } object t = Activator.CreateInstance(target); Var propetys = target.getProperties ().Where(x =>) x.IsDefined(typeof(ManualPropertyInjectionAttribute), true)); Foreach (var item in propetys) {// Get PropertyType propType = item.propertyType; object obj = this.CreateInstance(propType); // set item.setValue (t, obj); } var methods = target.getmethods ().Where(x => x.IsDefined(typeof(ManualMethodInjectionAttribute), true)).ToList(); Foreach (var item in methods) {var propType = item.getParameters ()[0].parameterType; object obj = this.CreateInstance(propType); item.Invoke(t,new object[] { obj}); } return t; } public TFrom Resolve<TFrom>() { return (TFrom)this.CreateInstance(typeof(TFrom)); }Copy the code
5. Single interface with multiple implementations

1. Pass an alias parameter and interface type combination Key during registration

 public void Register<TFrom, To>(string servicesName) where To : TFrom
 {
     string Key = typeof(TFrom).FullName + servicesName;
     if (!container.ContainsKey(Key))
     {
         container.Add(Key, typeof(To));
     }
 }
Copy the code

2. Pass in the alias when constructing the object, and find the instance type to be created when registering based on the target type and alias combination key

 public Tinterface Resolve<Tinterface>(string servicesName)
 {
   return (Tinterface)this.CreateInstance(typeof(Tinterface), servicesName);
 }
Copy the code
6. Construct the incoming instance type

1. Pass instance parameters when you must register, save them first, and pass them when you construct

Four,

The next step is to create objects using Emit based on existing code, add basic validation to improve robustness, add life cycle management, and AOP extensions.