控制反轉(IOC容器)-Autofac入門


注意:本文為原創文章,任何形式的轉載、引用(包括但不限於以上形式)等,須先征得作者同意,否則一切后果自負。

簡介

Autofac 是一個令人着迷的.NET IoC 容器。

它管理類之間的依賴關系。當應用程序的規模和復雜性隨着時間不斷增長時,也能易於更改。這是通過將常規 .NET 類視為組件來實現的

入門

將 Autofac 集成到我們的應用程序的基本模式是:

  • 時刻牢記用控制反轉(IOC)來構建我們的應用程序。
  • 添加Autofac引用。
  • 在應用程序啟動時…
  • 創建一個ContainerBuilder。
  • 注冊組件。
  • 構建容器並將其存儲以備后用。
  • 在應用程序執行期間…
  • 從容器創建一個生命周期范圍。
  • 使用生命周期范圍來解析(resolve)組件的實例。

構建應用程序

控制反轉背后的思想是:與其將應用程序中的類捆綁在一起,讓類“新建”它們的依賴關系,不如在類構造期間把依賴項傳遞進來,如果每次傳遞的依賴項不相同,則可以隨時切換依賴關系並調用相應的依賴項的實現。

對於我們的示例應用程序,我們將定義一個將當前時間寫出的類。但是,我們不希望將它綁定到Console,因為我們希望能夠稍后測試該類,或者在控制台不可用的地方使用它。

我們將盡可能地把寫出時間的機制抽象化,因為如果我們以后想把它變成一個寫出明天日期的程序版本,那么就可以快速實現該功能。

代碼如下:

public interface IOutput
{
	void Write(string content);
}
public class Output : IOutput
{
	public void Write(string content)
	{
		Console.WriteLine(content);
	}
}
public interface ITodayWriter
{
	void WriteDate();
}
public class TodayWriter : ITodayWriter
{
	private IOutput _output;

	public TodayWriter(IOutput output)
	{
		_output = output;
	}

	public void WriteDate()
	{
		_output.Write(DateTime.Now.ToString());
	}
}

現在我們有了一個合理的結構化的依賴集,下面我們就把Autofac加入進來!

添加 Autofac 引用

第一步是將Autofac引用添加到我們的項目中。

對於我們的示例,僅使用核心Autofac包就夠了,因為該包包含了我們所要使用的全部核心功能。

引入Autofac最簡單的方法是通過NuGet。

應用啟動

在應用程序啟動時,我們需要創建一個ContainerBuilder並在其中注冊我們的組件。

組件可以是一個表達式、.NET類型或其他代碼(該代碼公開一個或多個服務,並可以接受其他依賴項)。

簡單來說,考慮一個實現接口的 .NET 類型,像這樣:

public class SomeType : IService
{
}

我們可以用以下兩種方法來解決這個問題:

  • 作為類型本身,SomeType
  • 作為接口,IService

在這種情況下,組件是SomeType,它公開的服務是SomeType和IService(這一點你可能不太明白,不過沒關系,后面我們將會講解)。

在Autofac中,您可以使用以下內容進行注冊ContainerBuilder:

// 創建一個構造器
var builder = new ContainerBuilder();

// 注冊組件,並公開它的服務
builder.RegisterType<SomeType>().As<IService>();

// 如果你想把組件自身也注冊成服務,那么可以使用AsSelf()
builder.RegisterType<SomeType>().AsSelf().As<IService>();

對於我們的示例程序,我們需要注冊我們所有的組件(類)並公開它們的服務(接口),以便可以很好地連接起來。

我們還需要存儲容器,方便以后用它來解析類型。

using System;
using Autofac;

namespace DemoApp
{
  public class Program
  {
    private static IContainer _Container { get; set; }

    static void Main(string[] args)
    {
      var builder = new ContainerBuilder();
      
      builder.RegisterType<Output>().As<IOutput>();
      builder.RegisterType<TodayWriter>().As<ITodayWriter>();
      
      Container = builder.Build();
      
      //該方法我們后面實現它
      WriteDate();
    }
  }
}

現在我們有了一個容器其中所有組件都已注冊,並且它們公開了適當的服務。讓我們來使用它吧。

應用程序執行

在應用程序執行期間,我們需要使用我們注冊的組件。我們可以通過生命周期范圍解析它們來做到這一點。

容器本身就是一個生命周期范圍,從技術上講,我們可以直接從容器中解決問題。但是,我非常不建議直接從容器解析

當我們解析一個組件時,根據我們定義的實例范圍,會創建該對象的一個​​新實例。(解析一個組件大致相當於調用“new”來實例化一個類。這真的真的過於簡單化了,但從類比的角度來看,這個比較很恰當。)一些組件可能需要被釋放(就像它們實現了IDisposable一樣)——Autofac可以在生命周期范圍被釋放時自動為我們處理那些組件的釋放。

但是,容器在應用程序的整個生命周期內都存在。如果我們直接從容器中解決很多東西,等到最終,我們可能會有很多東西等待釋放。這非常的不好(如果我們這樣做,可能會發生“內存泄漏”的事故)。

相反,如果我們從容器創建一個子生命周期范圍並從中解析。當我們完成組件解析后,Autofac會幫我們自動釋放子作用域並為清理所有內容。

如果我們使用Autofac 集成庫時,這個子作用域的創建主要是自動為我們完成的,因此我們不必考慮它。

對於我們的示例應用程序,現在我們將實現“WriteDate”方法,它從一個子生命周期范圍獲取寫出結果,並在完成時自動處理該生命周期。

namespace DemoApp
{
  public class Program
  {
    private static IContainer Container { get; set; }

    static void Main(string[] args)
    {
      // 該內容在上面已經實現,不再重復
    }

    public static void WriteDate()
    {
      // 創建一個子生命周期范圍,解析ITodayWriter服務,調用方法,調用結束后被子生命周期范圍釋放掉
      using (var scope = Container.BeginLifetimeScope())
      {
        var writer = scope.Resolve<ITodayWriter>();
        writer.WriteDate();
      }
    }
  }
}

現在當我們運行程序時......

  • 該WriteDate方法創建了一個生命周期范圍,從中可以解析依賴項。這樣做是為了避免任何內存泄漏 - 如果ITodayWriter或其依賴項是一次性的,它們將在范圍被釋放時自動釋放。
  • 該WriteDate方法從生命周期范圍內手動解析ITodayWriter。(這里是“服務地點”。)在內部……
    • Autofac 看到ITodayWriter映射到TodayWriter,所以開始創建一個TodayWriter.
    • Autofac 認為在其構造函數中TodayWriter需要一個IOutput。(這是“構造函數注入”。)
    • Autofac 看到IOutput映射到Output,所以創建一個新Output實例。
    • Autofac 使用新Output實例來完成構建TodayWriter.
    • Autofac 返回完全構造的TodayWriter供WriteDate消費。
  • 對writer.WriteDate()的調用會轉到全新的TodayWriter.WriteDate(),因為這是已解決的問題。
  • Autofac生命周期范圍已釋放。從該生命周期范圍解析的任何一次性項也將被處理。

之后,如果我們想讓我們的應用程序編寫一個不同的日期,我們可以實現一個不同的ITodayWriter,然后在應用程序啟動時更改注冊。我們不需要更改任何其他類。這是不是非常的棒,這就是控制反轉!


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM