# C# 互操作性入门系列(三):平台调用中的数据封送处理
**C#互操作系列文章:**
1. [**C#互操作性入门系列(一):C#中互操作性介绍**](http://www.cnblogs.com/zhili/archive/2013/01/14/NetInterop.html)
2. [**C#互操作性入门系列(二):使用平台调用调用Win32 函数**](http://www.cnblogs.com/zhili/archive/2013/01/21/PInvoke.html)
3. [**C#互操作性入门系列(三):平台调用中的数据封送处理**](http://www.cnblogs.com/zhili/archive/2013/01/23/DataSend.html)
4. [**C#互操作性入门系列(四):在C# 中调用COM组件**](http://www.cnblogs.com/zhili/archive/2013/01/27/COMInterop.html)
**本专题概要**
* **数据封送介绍**
* **封送Win32数据类型**
* **封送字符串的处理**
* **封送结构体的处理**
* **封送类的处理**
* **小结**
**一、数据封送介绍**
看到这个专题时,大家的第一个疑问肯定是——什么是数据封送呢?(_这系列专题中采用假设朋友的提问方式来解说概念,就是希望大家带着问题去学习本专题内容,以及大家在平时的学习过程中也可以采用这个方式,个人觉得这个方式可以使自己学习效率有所提高,即使这样在学习的过程可能会显得慢了,但是这种方式会对你所看过的知识点会有一个更深的印象。远比看的很快,最后却发现记住的没多少强,在这里分享下这个学习方式,认为可以接受的朋友可以在平时的学习中可以尝试下的,如果觉得不好的话,相信大家肯定也会有自己更好的学习方式的。)_对于这个问题的解释是,**数据封送是——在托管代码中对非托管函数进行互操作时,需要通过方法的参数和返回值在托管内存和非托管内存之间传递数据的过程,数据封送处理的过程是由CLR(公共语言运行时)的封送处理服务(即封送拆送器)完成的**。
封送拆送器主要进行3项任务:
1. 将数据从托管类型转换为非托管类型,或从非托管类型转换为托管类型
2. 将经过类型转换的数据从托管代码内存复制到非托管内存,或从非托管内存复制到托管内存
3. 调用完成后,释放封送处理过程中分配的内存
**二、封送Win32数据类型**
对非托管代码进行互操作时,一定会有数据的封送处理。然而封送时需要处理的数据类型分为两种——可直接复制到本机结构中的类型(blittable)和非直接复制到本机结构中的类型(non-bittable)。下面就这两种数据类型分别做一个介绍。
**2.1 可直接复制到本机结构中的类型**
由于在托管代码和非托管代码中,数据类型在托管内存和非托管内存的表示形式不一样,因为这样的原因,所以我们需要对数据进行封送处理,以至于在托管代码中调用非托管函数时,把正确的传入参数传递给非托管函数和把正确的返回值返回给托管代码中。然而,并不是所有数据类型在两者内存的表现形式不一样的,这时候我们把在托管内存和非托管内存中有相同表现形式的数据类型称为——可直接复制到本机结构中的类型,**这些数据类型不需要封送拆送器进行任何特殊的处理就可以在托管和非托管代码之间传递**, 下面列出一些课直接复制到本机结构中的简单数据类型:
| Windows 数据类型 | 非托管数据类型 | 托管数据类型 | 托管数据类型解释 |
| --- | --- | --- | --- |
|BYTE/Uchar/UInt8 |unsigned char |**System.Byte** |无符号8位整型 |
|Sbyte/Char/Int8 |char |**System.SByte** |有符号8位整型 |
|Short/Int16 |short |**System.Int16** |有符号16位整型 |
|USHORT/WORD/UInt16/WCHAR |unsigned short |**System.UInt16** |无符号16位整型 |
|Bool/HResult/Int/Long |long/int |**System.Int32** |有符号32位整型 |
|DWORD/ULONG/UINT |unsigned long/unsigned int |**System.UInt32** |无符号32位整型 |
|INT64/LONGLONG |_int64 |**System.Int64** |有符号64位整型 |
|UINT64/DWORDLONG/ULONGLONG |_uint64 |**System.UInt64** |无符号64位整型 |
|INT_PTR/hANDLE/wPARAM |void*/int或_int64 |**System.IntPtr** |有符号指针类型 |
|HANDLE |void* |**System.UIntPtr** |无符号指针类型 |
|FLOAT |float |**System.Single** |单精度浮点数 |
|DOUBLE |double |**System.Double** |双精度浮点数 |
除了上表列出来的简单类型之外,还有一些复制类型也属于可直接复制到本机结构中的数据类型:
(1) 数据元素都是可直接复制到本机结构中的一元数组,如整数数组,浮点数组等
(2)只包含可直接复制到本机结构中的**格式化**值类型
(3)成员变量全部都是可复制到本机结构中的类型且作为**格式化**类型封送的类
上面提到的格式化指的是——在类型定义时,成员的内存布局在声明时就明确指定的类型。在代码中用StructLayout属性修饰被指定的类型,并将StructLayout的LayoutKind属性设置为Sequential或Explicit,例如:
```
using System.Runtime.InteropServices;
// 下面的结构体也属于可直接复制到本机结构中的类型
[StructLayout(LayoutKind.Sequential)]
public struct Point {
public int x;
public int y;
}
```
**2.2 非直接复制到本机结构中的类型**
如果一个类型不是可直接复制到本机结构中的类型,那么它就是非直接复制到本机结构中的类型。由于一些类型在托管内存和非托管内存的表现形式不一样,**所以对于这种类型,封送器需要对它们进行相应的类型转换之后再复制到被调用的函数中**,下面列出一些非直接复制到本机结构中的数据类型:
|Windows 数据类型 |非托管数据类型 |托管数据类型 |托管数据类型解释 |
| --- | --- | --- | --- |
|Bool |bool |**System.Boolean** |布尔类型 |
|WCHAR/TCHAR |char/ wchar_t |**System.Char** |ANSI字符/Unicode字符 |
|LPCSTR/LPCWSTR/LPCTSTR/LPSTR/LPWSTR/LPTSTR |const char*/const wchar_t*/char*/wchar_t* |**System.String** |ANSI字符串/Unicode字符串,如果非托管代码不需要更新此字符串时,此时用String类型在托管代码中声明字符串类型 |
|LPSTR/LPWSTR/LPTSTR |Char*/wchar_t* |**System.StringBuilder** |ANSI字符串/Unicode字符串,如果非托管代码需要更新此字符串,然后把更新的字符串传回托管代码中,此时用StringBuilder类型在托管代码中声明字符串 |
除了上表中列出的类型之外,还有很多其他类型属于非直接复制到本机结构中的类型,例如其他指针类型和句柄类型等。理解了blittable和non-blittable类型的区别之后,就可以在互操作过程更好地处理数据的封送,下面就具体的一些数据类型的封送问题做一个简单介绍
**三、封送字符串的处理**
在上一个专题中,我们已经涉及到字符串的封送问题了(上一个专题中使用了将字符串作为In参数传递给Win32 MessageBox 函数,具体可以查看上一个专题) 。所以在这部分将介绍——封送作为返回值的字符串,下面是一段演示代码,代码中主要是调用Win32 GetTempPath函数来获得返回返回临时路径,此时拆送器就需要把返回的字符串封送回托管代码中。
```
// 托管函数中的返回值封送回托管函数的例子
class Program
{
// Win32 GetTempPath函数的定义如下:
//DWORD WINAPI GetTempPath(
// _In_ DWORD nBufferLength,
// _Out_ LPTSTR lpBuffer
//); **// 主要是注意如何在托管代码中定义该函数原型 **
[DllImport("Kernel32.dll", CharSet = CharSet.Unicode, SetLastError=true)]
public static extern uint GetTempPath(int bufferLength, StringBuilder buffer);
static void Main(string[] args)
{
StringBuilder buffer = new StringBuilder(300);
uint tempPath=GetTempPath(300, buffer);
string path = buffer.ToString();
if (tempPath == 0)
{
int errorcode =Marshal.GetLastWin32Error();
Win32Exception win32expection = new Win32Exception(errorcode);
Console.WriteLine("调用非托管函数发生异常,异常信息为:" +win32expection.Message);
}
Console.WriteLine("调用非托管函数成功。");
Console.WriteLine("Temp 路径为:" + buffer);
Console.Read();
}
}
```
运行结果为: ![](https://box.kancloud.cn/2016-01-23_56a2eb3a05b0f.png)
**四、封送结构体的处理**
在我们实际调用Win32 API函数时,经常需要封送结构体和类等复制类型,下面就以Win32 函数GetVersionEx为例子来演示如何对作为参数的结构体进行封送处理。为了在托管代码中调用非托管代码,首先我们就要知道非托管函数的定义,下面是GetVersionEx非托管定义(更多关于该函数的信息可以参看MSDN链接:[http://msdn.microsoft.com/en-us/library/ms885648.aspx](http://msdn.microsoft.com/en-us/library/ms885648.aspx) ):
参数lpVersionInformation是一个指向 **OSVERSIONINFO**结构体的指针类型,所以我们在托管代码中为函数GetVersionEx函数之前,必须知道 **OSVERSIONINFO**结构体的非托管定义,然后再在托管代码中定义一个等价的结构体类型作为参数。以下是**OSVERSIONINFO结构体的非托管定义:**
```
typedef struct _OSVERSIONINFO{
DWORD dwOSVersionInfoSize; //在使用GetVersionEx之前要将此初始化为结构的大小
DWORD dwMajorVersion; //系统主版本号
DWORD dwMinorVersion; //系统次版本号
DWORD dwBuildNumber; //系统构建号
DWORD dwPlatformId; //系统支持的平台
TCHAR szCSDVersion[128]; //系统补丁包的名称
WORD wServicePackMajor; //系统补丁包的主版本
WORD wServicePackMinor; //系统补丁包的次版本
WORD wSuiteMask; //标识系统上的程序组
BYTE wProductType; //标识系统类型
BYTE wReserved; //保留,未使用
} OSVERSIONINFO;
```
知道了**OSVERSIONINFO**结构体在非托管代码中的定义之后, 现在我们就需要在托管代码中定义一个等价的结构,并且要保证两个结构体在内存中的布局相同。托管代码中的结构体定义如下:
```
// 因为Win32 GetVersionEx函数参数lpVersionInformation是一个指向 OSVERSIONINFO的数据结构
// 所以托管代码中定义个结构体,把结构体对象作为非托管函数参数
**[StructLayout(LayoutKind.Sequential,CharSet=CharSet.Unicode)]** public struct OSVersionInfo
{
public UInt32 OSVersionInfoSize; // 结构的大小,在调用方法前要初始化该字段
public UInt32 MajorVersion; // 系统主版本号
public UInt32 MinorVersion; // 系统此版本号
public UInt32 BuildNumber; // 系统构建号
public UInt32 PlatformId; // 系统支持的平台
// 此属性用于表示将其封送成内联数组
[MarshalAs(UnmanagedType.ByValTStr,SizeConst=128)]
public string CSDVersion; // 系统补丁包的名称
public UInt16 ServicePackMajor; // 系统补丁包的主版本
public UInt16 ServicePackMinor; // 系统补丁包的次版本
public UInt16 SuiteMask; //标识系统上的程序组
public Byte ProductType; //标识系统类型
public Byte Reserved; //保留,未使用
}
```
从上面的定义可以看出, 托管代码中定义的结构体有以下三个方面与非托管代码中的结构体是相同的:
* **字段声明的顺序**
* **字段的类型**
* **字段在内存中的大小**
并且在上面结构体的定义中,我们使用到了 **StructLayout** 属性,该属性属于System.Runtime.InteropServices命名空间(所以在使用平台调用技术必须添加这个额外的命名空间)。这个类的作用就是允许开发人员显式指定结构体或类中数据字段的内存布局,为了保证结构体中的数据字段在内存中的顺序与定义时一致,所以指定为 **LayoutKind.Sequential**(该枚举也是默认值)。 下面就具体看看在托管代码中调用的代码:
```
using System;
using System.ComponentModel;
using System.Runtime.InteropServices;
namespace 封送结构体的处理
{
class Program
{
// 对GetVersionEx进行托管定义
**// 为了传递指向结构体的指针并将初始化的信息传递给非托管代码,需要用ref关键字修饰参数
// 这里不能使用out关键字,如果使用了out关键字,CLR就不会对参数进行初始化操作,这样就会导致调用失败**
[DllImport("Kernel32",CharSet=CharSet.Unicode,EntryPoint="GetVersionEx")]
private static extern Boolean GetVersionEx_Struct(ref OSVersionInfo osVersionInfo);
// 因为Win32 GetVersionEx函数参数lpVersionInformation是一个指向 OSVERSIONINFO的数据结构
// 所以托管代码中定义个结构体,把结构体对象作为非托管函数参数
[StructLayout(LayoutKind.Sequential,CharSet=CharSet.Unicode)]
public struct OSVersionInfo
{
public UInt32 OSVersionInfoSize; // 结构的大小,在调用方法前要初始化该字段
public UInt32 MajorVersion; // 系统主版本号
public UInt32 MinorVersion; // 系统此版本号
public UInt32 BuildNumber; // 系统构建号
public UInt32 PlatformId; // 系统支持的平台
// 此属性用于表示将其封送成内联数组
[MarshalAs(UnmanagedType.ByValTStr,SizeConst=128)]
public string CSDVersion; // 系统补丁包的名称
public UInt16 ServicePackMajor; // 系统补丁包的主版本
public UInt16 ServicePackMinor; // 系统补丁包的次版本
public UInt16 SuiteMask; //标识系统上的程序组
public Byte ProductType; //标识系统类型
public Byte Reserved; //保留,未使用
}
// 获得操作系统信息
private static string GetOSVersion()
{
// 定义一个字符串存储版本信息
string versionName = string.Empty;
// 初始化一个结构体对象
OSVersionInfo osVersionInformation = new OSVersionInfo();
// 调用GetVersionEx 方法前,必须用SizeOf方法设置结构体中OSVersionInfoSize 成员
osVersionInformation.OSVersionInfoSize = (UInt32)Marshal.SizeOf(typeof(OSVersionInfo));
// 调用Win32函数
Boolean result = GetVersionEx_Struct(ref osVersionInformation);
if (!result)
{
// 如果调用失败,获得最后的错误码
int errorcode = Marshal.GetLastWin32Error();
Win32Exception win32Exc = new Win32Exception(errorcode);
Console.WriteLine("调用失败的错误信息为: " + win32Exc.Message);
// 调用失败时返回为空字符串
return string.Empty;
}
else
{
Console.WriteLine("调用成功");
switch (osVersionInformation.MajorVersion)
{
// 这里仅仅讨论 主版本号为6的情况,其他情况是一样讨论的
case 6:
switch (osVersionInformation.MinorVersion)
{
case 0:
if (osVersionInformation.ProductType == (Byte)0)
{
versionName = " Microsoft Windows Vista";
}
else
{
versionName = "Microsoft Windows Server 2008"; // 服务器版本
}
break;
case 1:
if (osVersionInformation.ProductType == (Byte)0)
{
versionName = " Microsoft Windows 7";
}
else
{
versionName = "Microsoft Windows Server 2008 R2";
}
break;
case 2:
versionName = "Microsoft Windows 8";
break;
}
break;
default:
versionName = "未知的操作系统";
break;
}
return versionName;
}
}
static void Main(string[] args)
{
string OS=GetOSVersion();
Console.WriteLine("当前电脑安装的操作系统为:{0}", OS);
Console.Read();
}
}
}
```
运行结果为: ![](https://box.kancloud.cn/2016-01-23_56a2eb3a1307c.jpg)
附上微软操作系统名和版本号的对应关系,大家可以参考下面的表对上面代码进行其他的讨论:
| **操作系统** | **版本号** |
| --- | --- |
| **Windows 8** | 6.2 |
| **Windows 7** | 6.1 |
| **Windows Server 2008 R2** | 6.1 |
| **Windows Server 2008** | 6.0 |
| **Windows Vista** | 6.0 |
| **Windows Server 2003 R2** | 5.2 |
| **Windows Server 2003** | 5.2 |
| **Windows XP** | 5.1 |
| **Windows 2000** | 5.0 |
**五、封送类的处理**
下面直接通过GetVersionEx函数进行封送类的处理的例子,具体代码如下:
```
using System;
using System.ComponentModel;
using System.Runtime.InteropServices;
namespace 封送类的处理
{
class Program
{
// 对GetVersionEx进行托管定义
// 由于类的定义中CSDVersion为String类型,String是非直接复制到本机结构类型,
// 所以封送拆送器需要进行复制操作。
// 为了是非托管代码能够获得在托管代码中对象设置的初始值(指的是OSVersionInfoSize字段,调用函数前首先初始化该值),
// 所以必须加上[In]属性;函数返回时,为了将结果复制到托管对象中,必须同时加上 [Out]属性
// 这里不能是用ref关键字,因为 OsVersionInfo是类类型,本来就是引用类型,如果加ref 关键字就是传入的为指针的指针了,这样就会导致调用失败
[DllImport("Kernel32", CharSet = CharSet.Unicode, EntryPoint = "GetVersionEx")]
private static extern Boolean GetVersionEx_Struct([In, Out] OSVersionInfo osVersionInfo);
// 获得操作系统信息
private static string GetOSVersion()
{
// 定义一个字符串存储操作系统信息
string versionName = string.Empty;
// 初始化一个类对象
OSVersionInfo osVersionInformation = new OSVersionInfo();
// 调用Win32函数
Boolean result = GetVersionEx_Struct(osVersionInformation);
if (!result)
{
// 如果调用失败,获得最后的错误码
int errorcode = Marshal.GetLastWin32Error();
Win32Exception win32Exc = new Win32Exception(errorcode);
Console.WriteLine("调用失败的错误信息为: " + win32Exc.Message);
// 调用失败时返回为空字符串
return string.Empty;
}
else
{
Console.WriteLine("调用成功");
switch (osVersionInformation.MajorVersion)
{
// 这里仅仅讨论 主版本号为6的情况,其他情况是一样讨论的
case 6:
switch (osVersionInformation.MinorVersion)
{
case 0:
if (osVersionInformation.ProductType == (Byte)0)
{
versionName = " Microsoft Windows Vista";
}
else
{
versionName = "Microsoft Windows Server 2008"; // 服务器版本
}
break;
case 1:
if (osVersionInformation.ProductType == (Byte)0)
{
versionName = " Microsoft Windows 7";
}
else
{
versionName = "Microsoft Windows Server 2008 R2";
}
break;
case 2:
versionName = "Microsoft Windows 8";
break;
}
break;
default:
versionName = "未知的操作系统";
break;
}
return versionName;
}
}
static void Main(string[] args)
{
string OS = GetOSVersion();
Console.WriteLine("当前电脑安装的操作系统为:{0}", OS);
Console.Read();
}
}
[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)]
public class OSVersionInfo
{
public UInt32 OSVersionInfoSize = (UInt32)Marshal.SizeOf(typeof(OSVersionInfo));
public UInt32 MajorVersion = 0;
public UInt32 MinorVersion = 0;
public UInt32 BuildNumber = 0;
public UInt32 PlatformId = 0;
// 此属性用于表示将其封送成内联数组
[MarshalAs(UnmanagedType.ByValTStr, SizeConst = 128)]
public string CSDVersion = null;
public UInt16 ServicePackMajor = 0;
public UInt16 ServicePackMinor = 0;
public UInt16 SuiteMask = 0;
public Byte ProductType = 0;
public Byte Reserved;
}
}
```
运行结果还是和上面使用结构体定义的一样,还是附上下图吧: ![](https://box.kancloud.cn/2016-01-23_56a2eb3a1307c.jpg)
**六、小结**
本专题主要介绍了几种类型的数据封送处理, 对于封送处理的一句话概括就是——**保证托管代码中定义的数据在内存中的布局与非托管代码中的内存布局相同,**专题中也列出了一些简单类型在非托管代码和托管代码中定义的对应关系,对于一些没有列出来的指针类型或回调函数等可以使用万能的**IntPtr**类型在托管代码中定义.然而本专题只是对数据封送做一个入门的介绍, 要真真掌握数据封送处理还要考虑很多其他的因素,这个就需要大家在平时工作中积累的。
- 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 领域驱动设计实战系列总结