# [C#基础知识系列]专题十七:深入理解动态类型
**本专题概要:**
* **动态类型介绍**
* **为什么需要动态类型**
* **动态类型的使用**
* **动态类型背后的故事**
* **动态类型的约束**
* **实现动态行为**
* **总结**
**引言:**
终于迎来了我们C# 4中特性了,C# 4主要有两方面的改善——Com 互操作性的改进和动态类型的引入,然而COM互操作性这里就不详细介绍的,对于.Net 互操作性我将会在另外一个专题中详细和大家分享下我所了解到的知识,本专题就和大家分享C# 4中的动态类型,对于动态类型,我刚听到这个名词的时候会有这些疑问的——动态类型到底是什么的呢? 知道动态类型大概是个什么的时候,肯定又会有这样的疑问——C# 4中为什么要引入动态类型的?(肯定引入之后可以完成我们之前不能做的事情了,肯定是有好处的),下面就具体介绍了动态类型有哪些内容的。
**一、动态类型介绍**
提到动态类型当然就要说下静态类型了,对于什么是静态类型呢? 大家都知道之前C#一直都是静态语言(指定的是没有引入动态类型之前,这里说明下,不是引入了动态类型后C#就是动态语言,只是引入动态类型后,为C#语言增添了动态语言的特性,C#仍然是静态语言),之所以称为静态语言,之前我们写代码时,例如 int i =5;这样的代码,此时i 我们已经明确知道它的类型为int了,然而这样的代码,**变量的类型的确定是在编译时确定的,对应的,如果类型的确定是在执行时才确定的类型,这样的类型就是动态类型(C# 4.0中新添加了一个dynamic 关键字来定义我们的动态类型)。**面对动态类型,C#编译器做的工作只是完成检查语法是否正确,但无法确定所调用的方法或属性是否正确(之所以会这样,主要还是因为动态类型是运行时才知道它的具体类型,所以编译器编译的时候肯定不知道类型,就没办法判断调用的方法或属性是不是存在和正确了,所以对于动态类型,将不能使用VS提供的智能提示的功能,这样写动态类型代码时就要求开发人员对于某个动态类型必须准确知道其类型后和所具有的方法和属性了,不能这些错误只能在运行程序的过程抛出异常的方式被程序员所发现。)
补充: 讲到dynamic关键字,也许大家会想到C# 3中的var关键字,这里这里补充说明下dynamic, var区别。var 关键字不过是一个指令,它告诉编译器根据变量的初始化表达式来推断类型。(记住var并不是类型),而C# 4中引入的dynamic是类型,但是编译时不属于CLR类型(指的int,string,bool,double等类型,运行时肯定CLR类型中一种的),它是包含了System.Dynamic.DynamicAttribute特性的System.Object类型,但与object又不一样,不一样主要体现在动态类型不会在编译时时执行显式转换,下面给出一段代码代码大家就会很容易看出区别了:
```
object obj = 10;
Console.WriteLine(obj.GetType());
// 使用object类型此时需要强制类型转换,不能编译器会出现编译错误
obj = (int)obj + 10;
dynamic dynamicnum = 10;
Console.WriteLine(dynamicnum.GetType());
// 对于动态类型而言,编译时编译器根本不知道它是什么类型,
// 所以编译器就判断不了dynamicnum的类型了,所以下面的代码不会出现编译时错误
// 因为dynamicnum有可能是int类型,编译器不知道该变量的具体类型不能凭空推测类型
// 当然也就不能提示我们编译时错误了
dynamicnum = dynamicnum + 10;
```
**二、为什么需要动态类型**
第一部分和大家介绍了什么是动态类型,对于动态类型,总结为一句话为——**运行时确定的类型。**然而大家了解了动态类型到底是什么之后,当然又会出现新的问题了,即动态类型有什么用的呢? C# 为什么好端端的引入动态类型增加程序员的负担呢? 事实并不是这样的,下面就介绍了动态类型到底有什么用,它并不是所谓给程序员带来负担,一定程度上讲是福音
**2.1 使用动态类型可以减少强制类型转换**
从第一部分的补充也可以看到,使用动态类型不需要类型转换是因为编译器根本在编译时的过程知道什么类型,既然不知道是什么类型,怎么判断该类型是否能进行什么操作,所以也就不会出现类似“运算符“+”无法应用于“object”和“int”类型的操作数“或者”不存在int类型到某某类型的隐式转换“的编译时错误了,可能这点用户,开发人员可能并不觉得多好的,因为动态类型没有智能提示的功能。 但是动态类型减少了强制类型转换的代码之后,可读性还是会有所增强。(这里又涉及到个人取舍问题的, 如果自己觉得那种方式方便就用那种的,没必要一定要用动态类型,主要是看那种方式可以让自己和其他开发人员更好理解)
**2.2 使用动态类型可以使C#静态语言中调用Python等动态语言**
对于这点,可能朋友有个疑问,为什么要在C#中使用Python这样的动态语言呢? 对于这个疑问,就和在C#中通过P/Invoke与本地代码交互,以及与COM互操作的道理一样,假设我们要实现的功能在C#类库中没有,然而在Python中存在时,此时我们就可以直接调用Python中存在的功能了。
**三、动态类型的使用**
前面两部分和大家介绍动态类型的一些基础知识的,了解完基础知识之后,大家肯定很迫不及待地想知道如何使用动态类型的,下面给出两个例子来演示动态类型的使用的。
3.1 C# 4 通过dynamic关键字来实现动态类型
```
dynamic dyn = 5;
Console.WriteLine(dyn.GetType());
dyn = "test string";
Console.WriteLine(dyn.GetType());
dynamic startIndex = 2;
string substring = dyn.Substring(startIndex);
Console.WriteLine(substring);
Console.Read();
```
运行结果为: ![](https://box.kancloud.cn/2016-01-23_56a2eb2d8f531.PNG)
3.2 在C#中调用Python动态语言(要运行下面的代码,必须下载并安装IronPython,IronPython 是在 .NET Framework 上实现的第一种动态语言。[http://ironpython.codeplex.com](http://ironpython.codeplex.com)下载 )
```
// 引入动态类型之后
// 可以在C#语言中与动态语言进行交互
// 下面演示在C#中使用动态语言Python
ScriptEngine engine = Python.CreateEngine();
Console.Write("调用Python语言的print函数输出: ");
// 调用Python语言的print函数来输出
engine.Execute("print 'Hello world'");
Console.Read();
```
运行结果: ![](https://box.kancloud.cn/2016-01-23_56a2eb2d9f315.PNG)
**四、动态类型背后的故事**
知道了如何在C#中调用动态语言之后,然而为什么C# 为什么可以使用动态类型呢?C#编译器到底在背后为我们动态类型做了些什么事情的呢? 对于这些问题,答案就是DLR(Dynamic Language Runtime,动态语言运行时),DLR使得C#中可以调用动态语言以及使用dynamic的动态类型。提到DLR时,可能大家会想到.Net Framework中的CLR(公共语言运行时),然而DLR 与CLR到底是什么关系呢?下面就看看.Net 4中的组件结构图,相信大家看完之后就会明白两者之间的区别:
![](https://box.kancloud.cn/2016-01-23_56a2eb2dab5f7.PNG)
从图中可以看出,DLR是建立在CLR的基础之上的,其实动态语言运行时**是动态语言和C#编译器用来动态执行代码的库,它不具有JIT编译,垃圾回收等功能。然而DLR在代码的执行过程中扮演的是什么样的角色呢? DLR所扮演的角色就是——DLR通过它的绑定器(binder)和调用点(callsite),元对象来把代码转换为表达式树,然后再把表达式树编译为IL代码,最后由CLR编译为本地代码(DLR就是帮助C#编译器来识别动态类型)。** 这里DLR扮演的角色并不是凭空想象出来的,而且查看它的反编译代码来推出来的,下面就具体给出一个例子来说明DLR背后所做的事情。C#源代码如下:
```
class Program
{
static void Main(string[] args)
{
dynamic text = "test text";
int startIndex = 2;
string substring = text.Substring(startIndex);
Console.Read();
}
}
```
通过Reflector工具查看生成的IL代码如下:
```
private static void Main(string[] args)
{
object text = "test text";
int startIndex = 2;
if (<Main>o__SiteContainer0.<>p__Site1 == null)
{
// 创建用于将dynamic类型隐式转换为字符串的调用点
<Main>o__SiteContainer0.<>p__Site1 = CallSite<Func<CallSite, object, string>>.Create(Binder.Convert(CSharpBinderFlags.None, typeof(string), typeof(Program)));
}
if (<Main>o__SiteContainer0.<>p__Site2 == null)
{
// 创建用于调用Substring函数的调用点
<Main>o__SiteContainer0.<>p__Site2 = CallSite<Func<CallSite, object, int, object>>.Create(Binder.InvokeMember(CSharpBinderFlags.None, "Substring", null, typeof(Program), new CSharpArgumentInfo[] { CSharpArgumentInfo.Create(CSharpArgumentInfoFlags.None, null), CSharpArgumentInfo.Create(CSharpArgumentInfoFlags.UseCompileTimeType, null) }));
}
// 调用调用点,首先调用<>p_Site2,即Substring方法,再调用<>P_Site1来将结果进行转换
string substring = <Main>o__SiteContainer0.<>p__Site1.Target(<Main>o__SiteContainer0.<>p__Site1, <Main>o__SiteContainer0.<>p__Site2.Target(<Main>o__SiteContainer0.<>p__Site2, text, startIndex));
Console.Read();
}
//编译器生成的内嵌类型为
[CompilerGenerated]
private static class <Main>o__SiteContainer0
{
// Fields
public static CallSite<Func<CallSite, object, string>> <>p__Site1;
public static CallSite<Func<CallSite, object, int, object>> <>p__Site2;
}
```
从IL代码中可以看出Main方法内包含两个动态操作,因为编译器生成的内嵌类型包含两个调用点(CallSite<T>,CallSite<T>即是System.Runtime.CompilerServices命名空间下的一个类,关于CallSite的具体信息可以查看MSDN中的介绍——[CallSite<T>](http://msdn.microsoft.com/zh-cn/library/dd402854.aspx) )字段,一个是调用Substring方法(即**[<>p__Site2](http://www.aisto.com/roeder/dotnet/Default.aspx?Target=code://动态类型Demo:1.0.0.0/动态类型Demo.Program.<Main>o__SiteContainer0/<>p__Site2:System.Runtime.CompilerServices.CallSite<System.Func<System.Runtime.CompilerServices.CallSite,Object,Int32,Object>>)**),一个是将结果(编译时时dynamic)动态地转换为字符串(即**<>p__Site1**),下面给出动态类型的执行过程(注意DLR中有一个缓存的概念): ![](https://box.kancloud.cn/2016-01-23_56a2eb2db9725.png)
**五、动态类型的约束**
相信通过前面几部分的介绍大家已经对动态类型有了一定的了解的,尤其是第四部分的介绍之后,大家应该对于动态类型的执行过程也有了一个清晰的认识了,然而有些函数时不能通过动态绑定来进行调用的,这里就涉及到类型类型的约束:
**5.1 不能用动态类型作为参数调用扩展方法**
不能用动态类型作为参数来调用扩展方法的原因是——调用点知道编译器所知道的静态类型,但是它不知道调用所在的源文件在哪里,以及using指令引入了哪些命名空间,所以在编译时调用点就找不到哪些扩展方法可以使用,所以就会出现编译时错误。下面给出一个简单的示例程序:
```
var numbers = Enumerable.Range(10, 10);
dynamic number = 4;
var error = numbers.Take(number); // 编译时错误
// 通过下面的方式来解决这个问题
// 1\. 将动态类型转换为正确的类型
var right1 = numbers.Take((int)number);
// 2\. 用调用静态方法的方式来进行调用
var right2 = Enumerable.Take(numbers, number);
```
**5.2 委托与动态类型不能隐式转换的限制**
如果需要将Lambda表达式,匿名方法转化为动态类型时,此时编译器必须知道委托的确切类型,不能不加强制转化就把他们设置为Delegae或object变量,此时不同string,int类型(因为前面int,string类型可以隐式转化为动态类型,编译器此时会把他们设置为object类型。但是匿名方法和Lambda表达式不能隐式转化为动态类型),如果需要完成这样的转换,此时必须强制指定委托的类型,下面是一个演示例子:
```
dynamic lambdarestrict = x => x + 1; // 编译时错误
// 解决方案
dynamic rightlambda =(Func<int,int>)( x=>x+1);
dynamic methodrestrict = Console.WriteLine; // 编译时错误
// 解决方案
dynamic rightmethod =(Action<string>)Console.WriteLine;
```
**5.3 动态类型不能调用构造函数和静态方法的限制**——即不能对动态类型调用构造函数或静态方法,因为此时编译器无法指定具体的类型。
**5.4 类型声明和泛型类型参数**
不能声明一个基类为dynamic的类型,也不能将dynamic用于类型参数的约束,或作为类型所实现的接口的一部分,下面看一些具体的例子来加深概念的理解:
```
// 基类不能为dynamic 类型
class DynamicBaseType : dynamic
{
}
// dynamic类型不能为类型参数的约束
class DynamicTypeConstrain<T> where T : dynamic
{
}
// 不能作为所实现接口的一部分
class DynamicInterface : IEnumerable<dynamic>
{
}
```
**六、实现动态的行为**
介绍了这么动态类型,是不是大家都迫不及待地想知道如果让自己的类型具有动态的行为呢? 然而实现动态行为有三种方式:
* 使用ExpandObject
* 使用DynamicObject
* 实现IDynamicMetaObjectProvider接口.
下面就从最简单的方式:
6.1 使用ExpandObject来实现动态的行为
View Code
```
using System;
// 引入额外的命名空间
using System.Dynamic;
namespace 自定义动态类型
{
class Program
{
static void Main(string[] args)
{
dynamic expand = new ExpandoObject();
// 动态为expand类型绑定属性
expand.Name = "Learning Hard";
expand.Age = 24;
// 动态为expand类型绑定方法
expand.Addmethod = (Func<int, int>)(x => x + 1);
// 访问expand类型的属性和方法
Console.WriteLine("expand类型的姓名为:"+expand.Name+" 年龄为: "+expand.Age);
Console.WriteLine("调用expand类型的动态绑定的方法:" +expand.Addmethod(5));
Console.Read();
}
}
}
```
运行的结果和预期的一样,运行结果为: ![](https://box.kancloud.cn/2016-01-23_56a2eb2dcac5a.png)
6.2 使用DynamicObject来实现动态行为
View Code
```
static void Main(string[] args)
{
dynamic dynamicobj = new DynamicType();
dynamicobj.CallMethod();
dynamicobj.Name = "Learning Hard";
dynamicobj.Age = "24";
Console.Read();
}
class DynamicType : DynamicObject
{
// 重写方法,
// TryXXX方法表示对对象的动态调用
public override bool TryInvokeMember(InvokeMemberBinder binder, object[] args, out object result)
{
Console.WriteLine(binder.Name +" 方法正在被调用");
result = null;
return true;
}
public override bool TrySetMember(SetMemberBinder binder, object value)
{
Console.WriteLine(binder.Name + " 属性被设置," + "设置的值为: " + value);
return true;
}
}
```
运行结果为: ![](https://box.kancloud.cn/2016-01-23_56a2eb2dd806b.png)
6.3 实现IDynamicMetaObjectProvider接口来实现动态行为
由于Dynamic类型在运行时来动态创建对象的,所以对该类型的每个成员的访问都会调用GetMetaObject方法来获得动态对象,然后通过这个动态对象来进行调用,所以实现IDynamicMetaObjectProvider接口,需要实现一个GetMetaObject方法来返回DynamicMetaObject对象,演示代码如下:
```
static void Main(string[] args)
{
dynamic dynamicobj2 = new DynamicType2();
dynamicobj2.Call();
Console.Read();
}
public class DynamicType2 : IDynamicMetaObjectProvider
{
public DynamicMetaObject GetMetaObject(Expression parameter)
{
Console.WriteLine("开始获得元数据......");
return new Metadynamic(parameter,this);
}
}
// 自定义Metadynamic类
public class Metadynamic : DynamicMetaObject
{
internal Metadynamic(Expression expression, DynamicType2 value)
: base(expression, BindingRestrictions.Empty, value)
{
}
// 重写响应成员调用方法
public override DynamicMetaObject BindInvokeMember(InvokeMemberBinder binder, DynamicMetaObject[] args)
{
// 获得真正的对象
DynamicType2 target = (DynamicType2)base.Value;
Expression self = Expression.Convert(base.Expression, typeof(DynamicType2));
var restrictions = BindingRestrictions.GetInstanceRestriction(self, target);
// 输出绑定方法名
Console.WriteLine(binder.Name + " 方法被调用了");
return new DynamicMetaObject(self, restrictions);
}
}
```
运行结果为: ![](https://box.kancloud.cn/2016-01-23_56a2eb2de632e.png)
**七、总结**
讲到这里动态类型的介绍就已经介绍完了,本专题差不多涵盖了动态类型中所有内容,希望通过本专题大家能够对C# 4.0中提出来的动态类型特性可以有进一步的了解,并且本专题也是这个系列中的最后一篇文章了,到这里C#基础知识系列也就结束了,后面我会整理出这个系列文章的一个索引,从而方便大家收藏,然而C#4中对COM互操作性也有很大的改善,关于互操作的内容将会在后面一个系列文章中和大家分享下我的学习体会。眼看都2点20了,该睡觉去了。
- C# 基础知识系列
- C# 基础知识系列 专题一:深入解析委托——C#中为什么要引入委托
- C# 基础知识系列 专题二:委托的本质论
- C# 基础知识系列 专题三:如何用委托包装多个方法——委托链
- C# 基础知识系列 专题四:事件揭秘
- C# 基础知识系列 专题五:当点击按钮时触发Click事件背后发生的事情
- C# 基础知识系列 专题六:泛型基础篇——为什么引入泛型
- C# 基础知识系列 专题七: 泛型深入理解(一)
- C# 基础知识系列 专题八: 深入理解泛型(二)
- C# 基础知识系列 专题九: 深入理解泛型可变性
- C#基础知识系列 专题十:全面解析可空类型
- C# 基础知识系列 专题十一:匿名方法解析
- C#基础知识系列 专题十二:迭代器
- C#基础知识 专题十三:全面解析对象集合初始化器、匿名类型和隐式类型
- C# 基础知识系列 专题十四:深入理解Lambda表达式
- C# 基础知识系列 专题十五:全面解析扩展方法
- C# 基础知识系列 专题十六:Linq介绍
- C#基础知识系列 专题十七:深入理解动态类型
- 你必须知道的异步编程 C# 5.0 新特性——Async和Await使异步编程更简单
- 全面解析C#中参数传递
- C#基础知识系列 全面解析C#中静态与非静态
- C# 基础知识系列 C#中易混淆的知识点
- C#进阶系列
- C#进阶系列 专题一:深入解析深拷贝和浅拷贝
- C#进阶系列 专题二:你知道Dictionary查找速度为什么快吗?
- C# 开发技巧系列
- C# 开发技巧系列 使用C#操作Word和Excel程序
- C# 开发技巧系列 使用C#操作幻灯片
- C# 开发技巧系列 如何动态设置屏幕分辨率
- C# 开发技巧系列 C#如何实现图片查看器
- C# 开发技巧 如何防止程序多次运行
- C# 开发技巧 实现属于自己的截图工具
- C# 开发技巧 如何使不符合要求的元素等于离它最近的一个元素
- C# 线程处理系列
- C# 线程处理系列 专题一:线程基础
- C# 线程处理系列 专题二:线程池中的工作者线程
- C# 线程处理系列 专题三:线程池中的I/O线程
- C# 线程处理系列 专题四:线程同步
- C# 线程处理系列 专题五:线程同步——事件构造
- C# 线程处理系列 专题六:线程同步——信号量和互斥体
- C# 多线程处理系列专题七——对多线程的补充
- C#网络编程系列
- C# 网络编程系列 专题一:网络协议简介
- C# 网络编程系列 专题二:HTTP协议详解
- C# 网络编程系列 专题三:自定义Web服务器
- C# 网络编程系列 专题四:自定义Web浏览器
- C# 网络编程系列 专题五:TCP编程
- C# 网络编程系列 专题六:UDP编程
- C# 网络编程系列 专题七:UDP编程补充——UDP广播程序的实现
- C# 网络编程系列 专题八:P2P编程
- C# 网络编程系列 专题九:实现类似QQ的即时通信程序
- C# 网络编程系列 专题十:实现简单的邮件收发器
- C# 网络编程系列 专题十一:实现一个基于FTP协议的程序——文件上传下载器
- C# 网络编程系列 专题十二:实现一个简单的FTP服务器
- C# 互操作性入门系列
- C# 互操作性入门系列(一):C#中互操作性介绍
- C# 互操作性入门系列(二):使用平台调用调用Win32 函数
- C# 互操作性入门系列(三):平台调用中的数据封送处理
- C# 互操作性入门系列(四):在C# 中调用COM组件
- CLR
- 谈谈: String 和StringBuilder区别和选择
- 谈谈:程序集加载和反射
- 利用反射获得委托和事件以及创建委托实例和添加事件处理程序
- 谈谈:.Net中的序列化和反序列化
- C#设计模式
- UML类图符号 各种关系说明以及举例
- C#设计模式(1)——单例模式
- C#设计模式(2)——简单工厂模式
- C#设计模式(3)——工厂方法模式
- C#设计模式(4)——抽象工厂模式
- C#设计模式(5)——建造者模式(Builder Pattern)
- C#设计模式(6)——原型模式(Prototype Pattern)
- C#设计模式(7)——适配器模式(Adapter Pattern)
- C#设计模式(8)——桥接模式(Bridge Pattern)
- C#设计模式(9)——装饰者模式(Decorator Pattern)
- C#设计模式(10)——组合模式(Composite Pattern)
- C#设计模式(11)——外观模式(Facade Pattern)
- C#设计模式(12)——享元模式(Flyweight Pattern)
- C#设计模式(13)——代理模式(Proxy Pattern)
- C#设计模式(14)——模板方法模式(Template Method)
- C#设计模式(15)——命令模式(Command Pattern)
- C#设计模式(16)——迭代器模式(Iterator Pattern)
- C#设计模式(17)——观察者模式(Observer Pattern)
- C#设计模式(18)——中介者模式(Mediator Pattern)
- C#设计模式(19)——状态者模式(State Pattern)
- C#设计模式(20)——策略者模式(Stragety Pattern)
- C#设计模式(21)——责任链模式
- C#设计模式(22)——访问者模式(Vistor Pattern)
- C#设计模式(23)——备忘录模式(Memento Pattern)
- C#设计模式总结
- WPF快速入门系列
- WPF快速入门系列(1)——WPF布局概览
- WPF快速入门系列(2)——深入解析依赖属性
- WPF快速入门系列(3)——深入解析WPF事件机制
- WPF快速入门系列(4)——深入解析WPF绑定
- WPF快速入门系列(5)——深入解析WPF命令
- WPF快速入门系列(6)——WPF资源和样式
- WPF快速入门系列(7)——深入解析WPF模板
- WPF快速入门系列(8)——MVVM快速入门
- WPF快速入门系列(9)——WPF任务管理工具实现
- ASP.NET 开发
- ASP.NET 开发必备知识点(1):如何让Asp.net网站运行在自定义的Web服务器上
- ASP.NET 开发必备知识点(2):那些年追过的ASP.NET权限管理
- ASP.NET中实现回调
- 跟我一起学WCF
- 跟我一起学WCF(1)——MSMQ消息队列
- 跟我一起学WCF(2)——利用.NET Remoting技术开发分布式应用
- 跟我一起学WCF(3)——利用Web Services开发分布式应用
- 跟我一起学WCF(3)——利用Web Services开发分布式应用
- 跟我一起学WCF(4)——第一个WCF程序
- 跟我一起学WCF(5)——深入解析服务契约 上篇
- 跟我一起学WCF(6)——深入解析服务契约 下篇
- 跟我一起学WCF(7)——WCF数据契约与序列化详解
- 跟我一起学WCF(8)——WCF中Session、实例管理详解
- 跟我一起学WCF(9)——WCF回调操作的实现
- 跟我一起学WCF(10)——WCF中事务处理
- 跟我一起学WCF(11)——WCF中队列服务详解
- 跟我一起学WCF(12)——WCF中Rest服务入门
- 跟我一起学WCF(13)——WCF系列总结
- .NET领域驱动设计实战系列
- .NET领域驱动设计实战系列 专题一:前期准备之EF CodeFirst
- .NET领域驱动设计实战系列 专题二:结合领域驱动设计的面向服务架构来搭建网上书店
- .NET领域驱动设计实战系列 专题三:前期准备之规约模式(Specification Pattern)
- .NET领域驱动设计实战系列 专题四:前期准备之工作单元模式(Unit Of Work)
- .NET领域驱动设计实战系列 专题五:网上书店规约模式、工作单元模式的引入以及购物车的实现
- .NET领域驱动设计实战系列 专题六:DDD实践案例:网上书店订单功能的实现
- .NET领域驱动设计实战系列 专题七:DDD实践案例:引入事件驱动与中间件机制来实现后台管理功能
- .NET领域驱动设计实战系列 专题八:DDD案例:网上书店分布式消息队列和分布式缓存的实现
- .NET领域驱动设计实战系列 专题九:DDD案例:网上书店AOP和站点地图的实现
- .NET领域驱动设计实战系列 专题十:DDD扩展内容:全面剖析CQRS模式实现
- .NET领域驱动设计实战系列 专题十一:.NET 领域驱动设计实战系列总结