C#中的依赖注入和IoC容器
在本文中,我们将通过用C#重构一个非常简单的代码示例来解释依赖注入和IoC容器。
简介:
依赖注入和IoC乍一看可能相当复杂,但它们非常容易学习和理解。
在本文中,我们将通过在C#中重构一个非常简单的代码示例来解释依赖注入和IoC容器。
要求:
构建一个允许用户查看可用产品并按名称搜索产品的应用程序。
第一次尝试:
我们将从创建分层架构开始。使用分层架构有多个好处,但我们不会在本文中列出它们,因为我们关注的是依赖注入。

下面是应用程序的类图:

首先,我们将从创建一个Product类开始:
public class Product
{
public Guid Id { get; set; }
public string Name { get; set; }
public string Description { get; set; }
}
然后,我们将创建数据访问层:
public class ProductDAL
{
private readonly List<Product> _products; public ProductDAL()
{
_products = new List<Product>
{
new Product { Id = Guid.NewGuid(), Name= "iPhone 9",
Description = "iPhone 9 mobile phone" },
new Product { Id = Guid.NewGuid(), Name= "iPhone X",
Description = "iPhone X mobile phone" }
};
} public IEnumerable<Product> GetProducts()
{
return _products;
} public IEnumerable<Product> GetProducts(string name)
{
return _products
.Where(p => p.Name.Contains(name))
.ToList();
}
}
然后,我们将创建业务层:
public class ProductBL
{
private readonly ProductDAL _productDAL; public ProductBL()
{
_productDAL = new ProductDAL();
} public IEnumerable<Product> GetProducts()
{
return _productDAL.GetProducts();
} public IEnumerable<Product> GetProducts(string name)
{
return _productDAL.GetProducts(name);
}
}
最后,我们将创建UI:
class Program
{
static void Main(string[] args)
{
ProductBL productBL = new ProductBL(); var products = productBL.GetProducts(); foreach (var product in products)
{
Console.WriteLine(product.Name);
} Console.ReadKey();
}
}
我们已经写在第一次尝试的代码是良好的工作成果,但有几个问题:
1.我们不能让三个不同的团队在每个层上工作。
2.业务层很难扩展,因为它依赖于数据访问层的实现。
3.业务层很难维护,因为它依赖于数据访问层的实现。
4.源代码很难测试。
第二次尝试:
高级别对象不应该依赖于低级别对象。两者都必须依赖于抽象。那么抽象概念是什么呢?
抽象是功能的定义。在我们的例子中,业务层依赖于数据访问层来检索图书。在C#中,我们使用接口实现抽象。接口表示功能的抽象。
让我们来创建抽象。
下面是数据访问层的抽象:
public interface IProductDAL
{
IEnumerable<Product> GetProducts();
IEnumerable<Product> GetProducts(string name);
}
我们还需要更新数据访问层:
public class ProductDAL : IProductDAL
我们还需要更新业务层。实际上,我们将更新业务层,使其依赖于数据访问层的抽象,而不是依赖于数据访问层的实现:
public class ProductBL
{
private readonly IProductDAL _productDAL; public ProductBL()
{
_productDAL = new ProductDAL();
} public IEnumerable<Product> GetProducts()
{
return _productDAL.GetProducts();
} public IEnumerable<Product> GetProducts(string name)
{
return _productDAL.GetProducts(name);
}
}
我们还必须创建业务层的抽象:
public interface IProductBL
{
IEnumerable<Product> GetProducts();
IEnumerable<Product> GetProducts(string name);
}
我们也需要更新业务层:
public class ProductBL : IProductBL
最终我们需要更新UI:
class Program
{
static void Main(string[] args)
{
IProductBL productBL = new ProductBL(); var products = productBL.GetProducts(); foreach (var product in products)
{
Console.WriteLine(product.Name);
} Console.ReadKey();
}
}
我们在第二次尝试中所做的代码是有效的,但我们仍然依赖于数据访问层的具体实现:
public ProductBL()
{
_productDAL = new ProductDAL();
}
那么,如何解决呢?
这就是依赖注入模式发挥作用的地方。
最终尝试
到目前为止,我们所做的工作都与依赖注入无关。
为了使处在较高级别的的业务层依赖于较低级别对象的功能,而没有具体的实现,必须由其他人创建类。其他人必须提供底层对象的具体实现,这就是我们所说的依赖注入。它的字面意思是我们将依赖对象注入到更高级别的对象中。实现依赖项注入的方法之一是使用构造函数进行依赖项注入。
让我们更新业务层:
public class ProductBL : IProductBL
{
private readonly IProductDAL _productDAL; public ProductBL(IProductDAL productDAL)
{
_productDAL = productDAL;
} public IEnumerable<Product> GetProducts()
{
return _productDAL.GetProducts();
} public IEnumerable<Product> GetProducts(string name)
{
return _productDAL.GetProducts(name);
}
}
基础设施必须提供对实现的依赖:
class Program
{
static void Main(string[] args)
{
IProductBL productBL = new ProductBL(new ProductDAL()); var products = productBL.GetProducts(); foreach (var product in products)
{
Console.WriteLine(product.Name);
} Console.ReadKey();
}
}
创建数据访问层的控制与基础设施结合在一起。这也称为控制反转。我们不是在业务层中创建数据访问层的实例,而是在基础设施的中创建它。 Main方法将把实例注入到业务逻辑层。因此,我们将低层对象的实例注入到高层对象的实例中。
这叫做依赖注入。
现在,如果我们看一下代码,我们只依赖于业务访问层中数据访问层的抽象,而业务访问层是使用的是数据访问层实现的接口。因此,我们遵循了更高层次对象和更低层次对象都依赖于抽象的原则,抽象是更高层次对象和更低层次对象之间的契约。
现在,我们可以让不同的团队在不同的层上工作。我们可以让一个团队处理数据访问层,一个团队处理业务层,一个团队处理UI。
接下来就显示了可维护性和可扩展性的好处。例如,如果我们想为SQL Server创建一个新的数据访问层,我们只需实现数据访问层的抽象并将实例注入基础设施中。
最后,源代码现在是可测试的了。因为我们在任何地方都使用接口,所以我们可以很容易地在较低的单元测试中提供另一个实现。这意味着较低的测试将更容易设置。
现在,让我们测试业务层。
我们将使用xUnit进行单元测试,使用Moq模拟数据访问层。
下面是业务层的单元测试:
public class ProductBLTest
{
private readonly List<Product> _products = new List<Product>
{
new Product { Id = Guid.NewGuid(), Name= "iPhone 9",
Description = "iPhone 9 mobile phone" },
new Product { Id = Guid.NewGuid(), Name= "iPhone X",
Description = "iPhone X mobile phone" }
};
private readonly ProductBL _productBL; public ProductBLTest()
{
var mockProductDAL = new Mock<IProductDAL>();
mockProductDAL
.Setup(dal => dal.GetProducts())
.Returns(_products);
mockProductDAL
.Setup(dal => dal.GetProducts(It.IsAny<string>()))
.Returns<string>(name => _products.Where(p => p.Name.Contains(name)).ToList()); _productBL = new ProductBL(mockProductDAL.Object);
} [Fact]
public void GetProductsTest()
{
var products = _productBL.GetProducts();
Assert.Equal(2, products.Count());
} [Fact]
public void SearchProductsTest()
{
var products = _productBL.GetProducts("X");
Assert.Single(products);
}
}
你可以看到,使用依赖项注入很容易设置单元测试。
IoC容器
容器只是帮助实现依赖注入的东西。容器,通常实现三种不同的功能:
1.注册接口和具体实现之间的映射
2.创建对象并解析依赖关系
3.释放
让我们实现一个简单的容器来注册映射并创建对象。
首先,我们需要一个存储映射的数据结构。我们将选择Hashtable。该数据结构将存储映射。
首先,我们将在容器的构造函数中初始化Hashtable。然后,我们将创建一个RegisterTransient方法来注册映射。最后,我们会创建一个创建对象的方法 Create :
public class Container
{
private readonly Hashtable _registrations; public Container()
{
_registrations = new Hashtable();
} public void RegisterTransient<TInterface, TImplementation>()
{
_registrations.Add(typeof(TInterface), typeof(TImplementation));
} public TInterface Create<TInterface>()
{
var typeOfImpl = (Type)_registrations[typeof(TInterface)];
if (typeOfImpl == null)
{
throw new ApplicationException($"Failed to resolve {typeof(TInterface).Name}");
}
return (TInterface)Activator.CreateInstance(typeOfImpl);
}
}
最终,我们会更新UI:
class Program
{
static void Main(string[] args)
{
var container = new Container();
container.RegisterTransient<IProductDAL, ProductDAL>(); IProductBL productBL = new ProductBL(container.Create<IProductDAL>());
var products = productBL.GetProducts(); foreach (var product in products)
{
Console.WriteLine(product.Name);
} Console.ReadKey();
}
}
现在,让我们在容器中实现Resolve方法。此方法将解决依赖关系。
Resolve方法如下:
public T Resolve<T>()
{
var ctor = ((Type)_registrations[typeof(T)]).GetConstructors()[0];
var dep = ctor.GetParameters()[0].ParameterType;
var mi = typeof(Container).GetMethod("Create");
var gm = mi.MakeGenericMethod(dep);
return (T)ctor.Invoke(new object[] { gm.Invoke(this, null) });
}
然后我们可以在UI中使用如下Resolve方法:
class Program
{
static void Main(string[] args)
{
var container = new Container();
container.RegisterTransient<IProductDAL, ProductDAL>();
container.RegisterTransient<IProductBL, ProductBL>(); var productBL = container.Resolve<IProductBL>();
var products = productBL.GetProducts(); foreach (var product in products)
{
Console.WriteLine(product.Name);
} Console.ReadKey();
}
}
在上面的源代码中,容器使用container.Resolve<IProductBL>()方法创建ProductBL类的一个对象。ProductBL类是IProductDAL的一个依赖项。因此,container.Resolve<IProductBL>() 通过自动创建并在其中注入一个ProductDAL对象返回ProductBL类的一个对象。这一切都在幕后进行。创建和注入ProductDAL对象是因为我们用IProductDAL注册了ProductDAL类型。
这是一个非常简单和基本的IoC容器,它向你展示了IoC容器背后的内容。就是这样。我希望你喜欢阅读这篇文章。
欢迎关注我的公众号,如果你有喜欢的外文技术文章,可以通过公众号留言推荐给我。

原文链接:https://www.codeproject.com/Articles/5274732/Dependency-Injection-and-IoC-Containers-in-Csharp
C#中的依赖注入和IoC容器的更多相关文章
- Atitit js中的依赖注入di ioc的实现
Atitit js中的依赖注入di ioc的实现 全类名(FQCN)为标识符1 混合请求模式1 使用类内 builder 即可..2 Service locator method走ok拦2 Jav ...
- 理解PHP 依赖注入|Laravel IoC容器
看Laravel的IoC容器文档只是介绍实例,但是没有说原理,之前用MVC框架都没有在意这个概念,无意中在phalcon的文档中看到这个详细的介绍,感觉豁然开朗,复制粘贴过来,主要是好久没有写东西了, ...
- 【转】理解 PHP 依赖注入 | Laravel IoC容器
Laravel框架的依赖注入确实很强大,并且通过容器实现依赖注入可以有选择性的加载需要的服务,减少初始化框架的开销,下面是我在网上看到的一个帖子,写的很好拿来与大家分享,文章从开始按照传统的类设计数据 ...
- ASP.NET Core中的依赖注入(1):控制反转(IoC)
ASP.NET Core在启动以及后续针对每个请求的处理过程中的各个环节都需要相应的组件提供相应的服务,为了方便对这些组件进行定制,ASP.NET通过定义接口的方式对它们进行了"标准化&qu ...
- ADO.NET .net core2.0添加json文件并转化成类注入控制器使用 简单了解 iTextSharp实现HTML to PDF ASP.NET MVC 中 Autofac依赖注入DI 控制反转IOC 了解一下 C# AutoMapper 了解一下
ADO.NET 一.ADO.NET概要 ADO.NET是.NET框架中的重要组件,主要用于完成C#应用程序访问数据库 二.ADO.NET的组成 ①System.Data → DataTable, ...
- ASP.NET Core中的依赖注入(2):依赖注入(DI)
IoC主要体现了这样一种设计思想:通过将一组通用流程的控制从应用转移到框架之中以实现对流程的复用,同时采用"好莱坞原则"是应用程序以被动的方式实现对流程的定制.我们可以采用若干设计 ...
- ASP.NET Core中的依赖注入(3): 服务的注册与提供
在采用了依赖注入的应用中,我们总是直接利用DI容器直接获取所需的服务实例,换句话说,DI容器起到了一个服务提供者的角色,它能够根据我们提供的服务描述信息提供一个可用的服务对象.ASP.NET Core ...
- ASP.NET Core中的依赖注入(5): ServiceProvider实现揭秘 【总体设计 】
本系列前面的文章我们主要以编程的角度对ASP.NET Core的依赖注入系统进行了详细的介绍,如果读者朋友们对这些内容具有深刻的理解,我相信你们已经可以正确是使用这些与依赖注入相关的API了.如果你还 ...
- spring 依赖注入(IOC DI)
依赖注入(IOC DI) 依赖注入的两种方式: 1. set注入 Spring要求使用set注入方式的时候,Bean需要提供一个无参数的构造方法.并提供一个属性的setter方法.例如: packag ...
随机推荐
- 冲刺随笔——Day_Seven
这个作业属于哪个课程 软件工程 (福州大学至诚学院 - 计算机工程系) 这个作业要求在哪里 团队作业第五次--Alpha冲刺 这个作业的目标 团队进行Alpha冲刺 作业正文 正文 其他参考文献 无 ...
- 【2020.12.03提高组模拟】A组反思
估计:40+10+0+0=50 实际:40+10+0+0=50 rank40 T1 赛时看到\(n,m\leq9\),我当机立断决定打表,暴力打了几个点之后发现在\(n\ne m\)且\(k\ne0\ ...
- Feign 自定义 ErrorDecoder (捕获 Feign 服务端异常)
问题描述 Feign 客户端捕获不到服务端抛出的异常 问题解决 重新 ErrorDecoder 即可,比如下面例子中在登录鉴权时想使用认证服务器抛出 OAuth2Exception 的异常,代码如下: ...
- Zuul 超时设置
问题描述 使用 Zuul 作为网关,偶发超时问题及第一次调用触发熔断问题 解决方案 超时问题 ribbon: ReadTimeout: 10000 SocketTimeout: 60000 第一次调用 ...
- 排序-InsertionSort 插入排序
插入排序 の implementation 插入排序就像打赌的时候,比如双扣.抽牌的时候,一次拿一张牌,这张牌和之前的牌一张张比较.选择把这张牌插入什么位置,排好顺序的位置后打牌更顺.要不然得一个一个 ...
- charles解决乱码
1.点击help ssl proxying ,install Charles Root Certificate 2.点击安装证书按钮 3:点击下一步按钮 4:选中将所有的证书都放入下列存储 点击浏览按 ...
- 通过Dbeaver创建表格的时候,设置主键
通过Dbeaver创建表格的时候,设置主键 Dbeaver介绍: 这是一个开源的数据库连接工具,你需要安装jre才可以使用这个软件 在使用Dbeaver创建表的时候,会发现,不能直观地设置主键 这 ...
- PyQt信号connect连接槽方法时报:native Qt signal is not callable错误
专栏:Python基础教程目录 专栏:使用PyQt开发图形界面Python应用 专栏:PyQt入门学习 老猿Python博文目录 老猿学5G博文目录 在将一个信号连接到槽方法时,程序异常退出,捕获异常 ...
- PyQt(Python+Qt)学习随笔:QToolBox工具箱currentItem对应的index、text、name、icon、ToolTip属性
老猿Python博文目录 专栏:使用PyQt开发图形界面Python应用 老猿Python博客地址 在Designer中,toolBox主要有如下属性: 可以看到,toolBox的属性主要是与当前项相 ...
- 第11.19节 Python 中正则表达式的扩展功能:前视断言和前视取反
一. 引言 在<第11.16节 Python正则元字符"()"(小括号)与组(group)匹配模式>中老猿介绍了组匹配模式的命名组功能及引用组功能,这两者都是组模式的扩 ...