1, Introduction
IOC inversion of control is a programming idea.
Let's first understand some concepts:
Dependency: it means that a class depends on another class.
Dependency Inversion Principle (DIP): one of the six principles of design pattern, is a software architecture design principle.
Control inversion (IoC): a software design principle in which the upper layer's dependence on the lower layer (i.e. the acquisition of the lower module) is handed over to a third party.
Dependency injection (DI): a way and means to implement IoC.
IoC container: a dependency injection framework used to map dependencies and manage the creation and life cycle of objects.
2, Rely on
Dependency means connection, and where it is used, it means dependency. Here is a simple example:
Dependency means connection, and where it is used, it means dependency. Here is a simple example:

class Program { class BMW { public string Show() { return "bmw"; } } class ChinesePeople { private BMW bmw = new BMW(); public void Run() { Console.WriteLine($"Open today{bmw.Show()}go to work"); } } static void Main(string[] args) { ChinesePeople people = new ChinesePeople(); BMW bmw = new BMW(); people.Run(); Console.Read(); } }
The above Chinese drive BMW to work. The client has two objects: Chinese and BMW. Among the Chinese, BMW is used. We can find three dependencies:
The client depends on the object ChinesePeople;
Client dependent object BMW;
ChinesePeople depends on the object BMW;
3, Dependency Inversion Principle
After a few days, there is a new demand. Chinese people not only have to drive a BMW to work, but also a Mercedes Benz to work. If we follow the above direct dependency method, we need to modify the ChinesePeople class to implement an overloaded method Run() with a parameter of BMW. Obviously, this is not a good design. We can't modify the ChinesePeople class every time we add a car (i.e. modify the lower module). It's too troublesome...
First, a simple analysis shows that the coupling relationship is a dependency relationship. If the dependency relationship is very heavy and affects the whole body, it will be difficult to maintain and expand. The less the coupling relationship, the more stable the system will be, so there should be less dependence.
definition:
A. High level modules should not depend on low-level modules, but both should rely on abstraction.
B. Abstraction should not depend on details, details should depend on abstraction.

In this figure, we find that the interface defined by the high-level module does not directly depend on the lower level module. The lower level module is responsible for implementing the interface defined by the high-level module. See the following example:
class Program { interface ICar { string Show(); } class BMW : ICar { public string Show() { return "bmw"; } } class BenZ : ICar { public string Show() { return "Benz"; } } interface IPeople { void Run(ICar car); } class ChinesePeople : IPeople { public void Run(ICar car) { Console.WriteLine($"Open today{car.Show()}go to work"); } } static void Main(string[] args) { ICar bmw = new BMW(); ICar benz = new BenZ(); IPeople people = new ChinesePeople(); people.Run(bmw); people.Run(benz); Console.Read(); } }
The operation results are as follows:

Analysis: in the above code, the ChinesePeople class no longer depends on the specific car, but on the abstract of the car, so that no matter what kind of car brand, Chinese people can drive to work, and there is no need to modify the ChinesePeople class. Think about it, is this good? We can conclude that the upper layer no longer depends on details. Compared with implementation oriented, interface oriented is better, because abstraction is more stable than details.
4, Control reversal
In the above example, we realize the isolation of specific people and specific cars. Specific people are only related to the interface of cars. But the specific object in the Main method in the Program is written dead, and the control power becomes smaller. When I want to modify the code when Americans drive Ford to work, I have to modify the code. How can I transfer the control power?
Here is a simple example (Please add the System.Configuration reference first):
interface ICar { string Show(); }
interface IPeople { void Run(ICar car); }
class BMW : ICar { public string Show() { return "bmw"; } }
class ChinesePeople : IPeople { public void Run(ICar car) { Console.WriteLine($"Open today{car.Show()}go to work"); } }
<?xml version="1.0" encoding="utf-8" ?> <configuration> <startup> <supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.6.1" /> </startup> <appSettings> <add key="People" value="LinkTo.Test.ConsoleIoC.ChinesePeople,LinkTo.Test.ConsoleIoC"/> <add key="Car" value="LinkTo.Test.ConsoleIoC.BMW,LinkTo.Test.ConsoleIoC"/> </appSettings> </configuration>
class Program { static void Main(string[] args) { #region reflection + configuration file to achieve Ioc string people = ConfigurationManager.AppSettings["People"]; string car = ConfigurationManager.AppSettings["Car"]; Assembly assemblyPeople = Assembly.Load(people.Split(',')[1]); Assembly assemblyCar = Assembly.Load(car.Split(',')[1]); Type typePeople = assemblyPeople.GetType(people.Split(',')[0]); Type typeCar = assemblyPeople.GetType(car.Split(',')[0]); IPeople ipeople = (IPeople)Activator.CreateInstance(typePeople); ICar icar = (ICar)Activator.CreateInstance(typeCar); ipeople.Run(icar); Console.Read(); #endregion } }
In the above code, we use the reflection + configuration file method to transfer the control of object creation to the configuration file, which is the so-called control inversion.
Analysis: control inversion refers to handing over the control of object creation to a third party, which can be an IoC container, which is equivalent to a simple factory. The factory will give us any object we want, so the dependency relationship will change. They (people and cars) all depend on the IoC container, and the dependency relationship between them will be established through the IoC container. (dependent objects are no longer obtained directly through new)
The operation results are as follows:

5, Dependency injection
As for the control reversal mentioned above, we understand that it is the transfer of control, which is our purpose. Configuration file + reflection is an implementation, while dependency injection provides an idea, or a means to implement IoC.
Dependency injection is to transfer the creation and binding of objects to the outside of the dependent object. What methods are generally used to implement it?
Method 1: constructor injection
class ChinesePeopleConstructor { private readonly ICar _car; //Dependency injection: constructor injection public ChinesePeopleConstructor(ICar car) { _car = car; } public void Run() { Console.WriteLine($"Open today{_car.Show()}go to work"); } }
class Program { static void Main(string[] args) { #region dependency injection: constructor injection ICar bmw = new BMW(); ChinesePeopleConstructor people = new ChinesePeopleConstructor(bmw); people.Run(); Console.Read(); #endregion } }
Method 2: attribute injection
class ChinesePeopleProperty { //Dependency injection: attribute injection public ICar Car { get; set; } public void Run() { Console.WriteLine($"Open today{Car.Show()}go to work"); } }
class Program { static void Main(string[] args) { #region dependency injection: attribute injection ICar bmw = new BMW(); ChinesePeopleProperty people = new ChinesePeopleProperty { Car = bmw }; people.Run(); Console.Read(); #endregion } }
Method 3: interface injection
interface IDependent { void SetDependent(ICar icar); }
class ChinesePeopleInterface : IDependent { private ICar _car; //Dependency injection: interface injection public void SetDependent(ICar car) { _car = car; } public void Run() { Console.WriteLine($"Open today{_car.Show()}go to work"); } }
class Program { static void Main(string[] args) { #region dependency injection: interface injection ICar bmw = new BMW(); ChinesePeopleInterface people = new ChinesePeopleInterface(); people.SetDependent(bmw); people.Run(); Console.Read(); #endregion } }
6, IoC container
IoC container is a DI framework. Its main functions are as follows:
A. Dynamically create and inject dependent objects;
B. Manage object life cycle;
C. Mapping dependencies;
Common IoC containers: Spring.NET, Castle Windsor, Ninject, Autofac, Unity...
6.1 use of Unity container
In the previous C# AOP learning notes [using entib \ PIAB Unity to implement AOP (with configuration)], IoC has been implemented using Unity container. Let's take another look at the configuration file:

If only IoC is required and AOP is not required, the container is like this:
<configuration> <configSections> <section name="unity" type="Microsoft.Practices.Unity.Configuration.UnityConfigurationSection, Unity.Configuration"/> </configSections> <unity> <sectionExtension type="Microsoft.Practices.Unity.InterceptionExtension.Configuration.InterceptionConfigurationExtension, Unity.Interception.Configuration"/> <containers> <container name="IoCContainer"> <!--Registration matching rule: the complete type name is in front of it, and the type name is in the back dll name.--> <register type="LinkTo.Test.ConsoleAop.UnityConfigAOP.IUserProcessor,LinkTo.Test.ConsoleAop" mapTo="LinkTo.Test.ConsoleAop.UnityConfigAOP.UserProcessor,LinkTo.Test.ConsoleAop"></register> </container> </containers> </unity> </configuration>
As can be seen from IoC's registration matching rules, the full type name is in front of it, and the dll is in the back. This is more powerful. If a system has extended functions or personality requirements, it only needs to configure and use a new dll, and the original system does not need to change the code. In this way, in addition to complying with the "opening and closing principle", it is a very powerful weapon for building a configurable and scalable system.