# WPF快速入门系列(2)——深入解析依赖属性
## 一、引言
感觉最近都颓废了,好久没有学习写博文了,出于负罪感,今天强烈逼迫自己开始更新WPF系列。尽管最近看到一篇WPF技术是否老矣的文章,但是还是不能阻止我系统学习WPF。今天继续分享WPF中一个最重要的知识点——依赖属性。
## 二、依赖属性的全面解析
听到依赖属性,自然联想到C#中属性的概念。C#中属性是抽象模型的核心部分,而依赖属性是专门基于WPF创建的。在WPF库实现中,依赖属性使用普通的C#属性进行了包装,使得我们可以通过和以前一样的方式来使用依赖属性,但我们必须明确,在WPF中我们大多数都在使用依赖属性,而不是使用属性。依赖属性重要性在于,在WPF核心特性,如动画、数据绑定以及样式中都需要使用到依赖属性。既然WPF引入了依赖属性,也自然有其引入的道理。WPF中的依赖属性主要有以下三个优点:
* 依赖属性加入了属性变化通知、限制、验证等功能。这样可以使我们更方便地实现应用,同时大大减少了代码量。许多之前需要写很多代码才能实现的功能,在WPF中可以轻松实现。
* 节约内存:在WinForm中,每个UI控件的属性都赋予了初始值,这样每个相同的控件在内存中都会保存一份初始值。而WPF依赖属性很好地解决了这个问题,它内部实现使用哈希表存储机制,对多个相同控件的相同属性的值都只保存一份。关于依赖属性如何节约内存的更多内容参考:[WPF的依赖属性是怎么节约内存的](http://www.cnblogs.com/zoupeiyang/archive/2011/07/22/2113778.html)
* 支持多种提供对象:可以通过多种方式来设置依赖属性的值。可以配合表达式、样式和绑定来对依赖属性设置值。
## 2.1 依赖属性的定义
上面介绍了依赖属性所带来的好处,这时候,问题又来了,怎样自己定义一个依赖属性呢?C#属性的定义大家再熟悉不过了。下面通过把C#属性进行改写成依赖属性的方式来介绍依赖属性的定义。下面是一个属性的定义:
在把上面属性改写为依赖属性之前,下面总结下定义依赖属性的步骤:
1. 让依赖属性的所在类型继承自[DependencyObject](http://msdn.microsoft.com/zh-cn/library/system.windows.dependencyobject(v=vs.110).aspx)类。
2. 使用public static 声明一个[DependencyProperty](http://msdn.microsoft.com/zh-cn/library/system.windows.dependencyproperty(v=vs.110).aspx)的变量,该变量就是真正的依赖属性。
3. 在类型的静态构造函数中通过[Register](http://msdn.microsoft.com/zh-cn/library/ms597501(v=vs.110).aspx)方法完成依赖属性的元数据注册。
4. 提供一个依赖属性的包装属性,通过这个属性来完成对依赖属性的读写操作。
根据上面的四个步骤,下面来把Name属性来改写成一个依赖属性,具体的实现代码如下所示:
```
// 1\. 使类型继承DependencyObject类
public class Person : DependencyObject
{
// 2\. 声明一个静态只读的DependencyProperty 字段
public static readonly DependencyProperty nameProperty;
static Person()
{
// 3\. 注册定义的依赖属性
nameProperty = DependencyProperty.Register("Name", typeof(string), typeof(Person),
new PropertyMetadata("Learning Hard",OnValueChanged));
}
// 4\. 属性包装器,通过它来读取和设置我们刚才注册的依赖属性
public string Name
{
get { return (string)GetValue(nameProperty); }
set { SetValue(nameProperty, value); }
}
private static void OnValueChanged(DependencyObject dpobj, DependencyPropertyChangedEventArgs e)
{
// 当只发生改变时回调的方法
}
}
```
从上面代码可以看出,**依赖属性是通过调用DependencyObject的GetValue和SetValue来对依赖属性进行读写的**。它使用哈希表来进行存储的,对应的Key就是属性的HashCode值,而值(Value)则是注册的DependencyPropery;而C#中的属性是类私有字段的封装,可以通过对该字段进行操作来对属性进行读写。总结为:**属性是字段的包装,WPF中使用属性对依赖属性进行包装。**
## 2.2 依赖属性的优先级
WPF允许在多个地方设置依赖属性的值,则自然就涉及到依赖属性获取值的优先级问题。例如下面XMAL代码,我们在三个地方设置了按钮的背景颜色,那最终按钮会读取那个设置的值呢?是Green、Yellow还是Red?
```
<Window x:Class="DPSample.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="MainWindow" Height="350" Width="525">
<Grid>
<Button x:Name="myButton" **Background="Green"** Width="100" Height="30">
<Button.Style>
<Style TargetType="{x:Type Button}">
<Setter **Property="Background" Value="Yellow"**/>
<Style.Triggers>
<Trigger Property="IsMouseOver" Value="True">
<Setter **Property="Background" Value="Red"** />
</Trigger>
</Style.Triggers>
</Style>
</Button.Style>
Click Me
</Button>
</Grid>
</Window>
```
上面按钮的背景颜色是Green。之所以背景色是Green,是因为WPF每访问一个依赖属性,它都会按照下面的顺序由高到底处理该值。具体优先级如下图所示:
![](https://box.kancloud.cn/2016-01-23_56a2eb40ecc1f.png)
在上面XAML中,按钮的本地值设置的是Green,自定义Style Trigger设置的为Red,自定义的Style Setter设置的为Yellow,由于这里的本地值的优先级最高,所以按钮的背景色或者的是Green值。如果此时把本地值Green去掉的话,此时按钮的背景颜色是Yellow而不是Red。这里尽管Style Trigger的优先级比Style Setter高,但是由于此时Style Trigger的IsMouseOver属性为false,即鼠标没有移到按钮上,一旦鼠标移到按钮上时,此时按钮的颜色就为Red。此时才会体现出Style Trigger的优先级比Style Setter优先级高。所以上图中优先级是比较理想情况下,很多时候还需要具体分析。
## 2.3 依赖属性的继承
依赖属性是可以被继承的,即父元素的相关设置会自动传递给所有的子元素。下面代码演示了依赖属性的继承。
```
<Window x:Class="Custom_DPInherited.DPInherited"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
mc:Ignorable="d"
d:DesignHeight="300" d:DesignWidth="300"
FontSize="18"
Title="依赖属性的继承">
<StackPanel >
<Label Content="继承自Window的FontSize" />
<Label Content="显式设置FontSize"
TextElement.FontSize="36"/>
<StatusBar>Statusbar没有继承自Window的FontSize</StatusBar>
</StackPanel>
</Window>
```
上面的代码的运行效果如下图所示:
![](https://box.kancloud.cn/2016-01-23_56a2eb410c193.png)
在上面XAML代码中。Window.FontSize设置会影响所有内部子元素字体大小,这就是依赖属性的继承。如第一个Label没有定义FontSize,所以它继承了Window.FontSize值。但一旦子元素提供了显式设置,这种继承就会被打断,所以Window.FontSize值对于第二个Label不再起作用。
这时,你可能已经发现了问题:StatusBar没有显式设置FontSize值,但它的字体大小没有继承Window.FontSize的值,而是保持了系统的默认值。那这是什么原因呢?其实导致这样的问题:并不是所有元素都支持属性值继承的,如StatusBar、Tooptip和Menu控件。另外,StatusBar等控件截获了从父元素继承来的属性,并且该属性也不会影响StatusBar控件的子元素。例如,如果我们在StatusBar中添加一个Button。那么这个Button的FontSize属性也不会发生改变,其值为默认值。
前面介绍了依赖属性的继承,那我们如何把自定义的依赖属性设置为可被其他控件继承呢?通过[AddOwer](http://msdn.microsoft.com/zh-cn/library/ms597484(v=vs.110).aspx)方法可以依赖属性的继承。具体的实现代码如下所示:
```
1 public class CustomStackPanel : StackPanel
2 {
3 public static readonly DependencyProperty MinDateProperty;
4
5 static CustomStackPanel()
6 {
7 MinDateProperty = DependencyProperty.Register("MinDate", typeof(DateTime), typeof(CustomStackPanel), new FrameworkPropertyMetadata(DateTime.MinValue, FrameworkPropertyMetadataOptions.Inherits));
8 }
9
10 public DateTime MinDate
11 {
12 get { return (DateTime)GetValue(MinDateProperty); }
13 set { SetValue(MinDateProperty, value); }
14 }
15 }
16
17 public class CustomButton :Button
18 {
19 private static readonly DependencyProperty MinDateProperty;
20
21 static CustomButton()
22 {
23 // AddOwner方法指定依赖属性的所有者,从而实现依赖属性的继承,即CustomStackPanel的MinDate属性被CustomButton控件继承。
24 // 注意FrameworkPropertyMetadataOptions的值为Inherits
25 MinDateProperty = CustomStackPanel.MinDateProperty.AddOwner(typeof(CustomButton), new FrameworkPropertyMetadata(DateTime.MinValue, FrameworkPropertyMetadataOptions.Inherits));
26 }
27
28 public DateTime MinDate
29 {
30 get { return (DateTime)GetValue(MinDateProperty); }
31 set { SetValue(MinDateProperty, value); }
32 }
33 }
```
接下来,你可以在XAML中进行测试使用,具体的XAML代码如下:
```
<Window x:Class="Custom_DPInherited.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:Custom_DPInherited"
xmlns:sys="clr-namespace:System;assembly=mscorlib"
Title="实现自定义依赖属性的继承" Height="350" Width="525">
<Grid>
<local:CustomStackPanel x:Name="customStackPanle" MinDate="{x:Static sys:DateTime.Now}">
<!--CustomStackPanel的依赖属性-->
<ContentPresenter Content="{Binding Path=MinDate, ElementName=customStackPanle}"/>
<local:CustomButton Content="{Binding RelativeSource={x:Static RelativeSource.Self}, Path=MinDate}" Height="25"/>
</local:CustomStackPanel>
</Grid>
</Window>
```
上面XAML代码中,显示设置了CustomStackPanel的MinDate的值,而在CustomButton中却没有显式设置其MinDate值。CustomButton的Content属性的值是通过绑定MinDate属性来进行获取的,关于绑定的更多内容会在后面文章中分享。在这里CustomButton中并没有设置MinDate的值,但是CustomButton的Content的值却是当前的时间,从而可以看出,此时CustomButton的MinDate属性继承了CustomStackPanel的MinDate的值,从而设置了其Content属性。最终的效果如下图所示:
![](https://box.kancloud.cn/2016-01-23_56a2eb411cdf7.png)
## 2.4 只读依赖属性
在C#属性中,我们可以通过设置只读属性来防止外界恶意更改该属性值,同样,在WPF中也可以设置只读依赖属性。如[IsMouseOver](http://msdn.microsoft.com/zh-cn/library/system.windows.uielement.ismouseover(v=vs.110).aspx)就是一个只读依赖属性。那我们如何创建一个只读依赖属性呢?其实只读的依赖属性的定义方式与一般依赖属性的定义方式基本一样。只读依赖属性仅仅是用[DependencyProperty.RegisterReadonly](http://msdn.microsoft.com/zh-cn/library/system.windows.dependencyproperty.registerreadonly(v=vs.110).aspx)替换了DependencyProperty.Register而已。下面代码实现了一个只读依赖属性。
```
1 public partial class MainWindow : Window
2 {
3 public MainWindow()
4 {
5 InitializeComponent();
6
7 // 内部使用SetValue来设置值
8 SetValue(counterKey, 8);
9 }
10
11 // 属性包装器,只提供GetValue,你也可以设置一个private的SetValue进行限制。
12 public int Counter
13 {
14 get { return (int)GetValue(counterKey.DependencyProperty); }
15 }
16
17 // 使用RegisterReadOnly来代替Register来注册一个只读的依赖属性
18 private static readonly DependencyPropertyKey counterKey =
19 DependencyProperty.RegisterReadOnly("Counter",
20 typeof(int),
21 typeof(MainWindow),
22 new PropertyMetadata(0));
23 }
```
对应的XAML代码为:
```
<Window x:Class="ReadOnlyDP.MainWindow"
Name="ThisWin"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="ReadOnly Dependency Property" Height="350" Width="525">
<Grid>
<Viewbox>
<TextBlock Text="{Binding ElementName=ThisWin, Path=Counter}"/>
</Viewbox>
</Grid>
</Window>
```
此时Counter包装的counterKey就是一个只读依赖属性,因为其定义为private的,所以在类外也不能使用DependencyObject.SetValue方法来对其值,而包装的Counter属性又只提供了GetValue方法,所以类外部只能对该依赖属性进行读取,而不能对其赋值。此时运行效果如下图所示。
![](https://box.kancloud.cn/2016-01-23_56a2eb412bb03.png)
## 2.5 附加属性
WPF中还有一类特殊的属性——附加属性。附加是一种特殊的依赖属性。它允许给一个对象添加一个值,而该对象可能对这个值一无所知。附加属性最常见的例子就是布局容器中DockPanel类中的Dock附加属性和Grid类中Row和Column附加属性。那问题又来了,我们怎样在自己的类中定义一个附加属性呢?其实定义附加属性和定义一般的依赖属性一样没什么区别,只是用RegisterAttached方法代替了Register方法罢了。下面代码演示了附加属性的定义。
```
public class AttachedPropertyClass
{
// 通过使用RegisterAttached来注册一个附加属性
public static readonly DependencyProperty IsAttachedProperty =
DependencyProperty.RegisterAttached("IsAttached", typeof(bool), typeof(AttachedPropertyClass),
new FrameworkPropertyMetadata((bool)false));
// 通过静态方法的形式暴露读的操作
public static bool GetIsAttached(DependencyObject dpo)
{
return (bool)dpo.GetValue(IsAttachedProperty);
}
public static void SetIsAttached(DependencyObject dpo, bool value)
{
dpo.SetValue(IsAttachedProperty, value);
}
}
```
在上面代码中,IsAttached就是一个附加属性,附加属性没有采用CLR属性进行封装,而是使用静态SetIsAttached方法和GetIsAttached方法来存取IsAttached值。这两个静态方法内部一样是调用SetValue和GetValue来对附加属性读写的。
## 2.6 依赖属性验证和强制
在定义任何类型的属性时,都需要考虑错误设置属性的可能性。对于传统的CLR属性,可以在属性的设置器中进行属性值的验证,不满足条件的值可以抛出异常。但对于依赖属性来说,这种方法不合适,因为依赖属性通过SetValue方法来直接设置其值的。然而WPF有其代替的方式,WPF中提供了两种方法来用于验证依赖属性的值。
* ValidateValueCallback:该回调函数可以接受或拒绝新值。该值可作为[DependencyProperty.Register](http://msdn.microsoft.com/zh-cn/library/ms597501(v=vs.110).aspx)方法的一个参数。
* CoerceValueCallback:该回调函数可将新值强制修改为可被接受的值。例如某个依赖属性Age的值范围是0到120,在该回调函数中,可以对设置的值进行强制修改,对于不满足条件的值,强制修改为满足条件的值。如当设置为负值时,可强制修改为0。该回调函数可作为[PropertyMetadata](http://msdn.microsoft.com/zh-cn/library/ms557328(v=vs.110).aspx)构造函数参数进行传递。
当应用程序设置一个依赖属性时,所涉及的验证过程如下所示:
1. 首先,CoerceValueCallback方法可以修改提供的值或返回[DependencyProperty.UnsetValue](http://msdn.microsoft.com/zh-cn/library/system.windows.dependencyproperty.unsetvalue(v=vs.110).aspx)。
2. 如果CoerceValueCallback方法强制修改了提供的值,此时会激活ValidateValueCallback方法进行验证,如果该方法返回为true,表示该值合法,被认为可被接受的,否则拒绝该值。不像CoerceValueCallback方法,ValidateValueCallback方法不能访问设置属性的实际对象,这意味着你不能检查其他属性值。即该方法中不能对类的其他属性值进行访问。
3. 如果上面两个阶段都成功的话,最后会触发PropertyChangedCallback方法来触发依赖属性值的更改。
下面代码演示了基本的流程。
```
1 class Program
2 {
3 static void Main(string[] args)
4 {
5 SimpleDPClass sDPClass = new SimpleDPClass();
6 sDPClass.SimpleDP = 2;
7 Console.ReadLine();
8 }
9 }
10
11 public class SimpleDPClass : DependencyObject
12 {
13 public static readonly DependencyProperty SimpleDPProperty =
14 DependencyProperty.Register("SimpleDP", typeof(double), typeof(SimpleDPClass),
15 new FrameworkPropertyMetadata((double)0.0,
16 FrameworkPropertyMetadataOptions.None,
17 new PropertyChangedCallback(OnValueChanged),
18 new CoerceValueCallback(CoerceValue)),
19 new ValidateValueCallback(IsValidValue));
20
21 public double SimpleDP
22 {
23 get { return (double)GetValue(SimpleDPProperty); }
24 set { SetValue(SimpleDPProperty, value); }
25 }
26
27 private static void OnValueChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
28 {
29 Console.WriteLine("当值改变时,我们可以做的一些操作,具体可以在这里定义: {0}", e.NewValue);
30 }
31
32 private static object CoerceValue(DependencyObject d, object value)
33 {
34 Console.WriteLine("对值进行限定,强制值: {0}", value);
35 return value;
36 }
37
38 private static bool IsValidValue(object value)
39 {
40 Console.WriteLine("验证值是否通过,返回bool值,如果返回True表示验证通过,否则会以异常的形式暴露: {0}", value);
41 return true;
42 }
43 }
```
其运行结果如下图所示:
![](https://box.kancloud.cn/2016-01-23_56a2eb4142e2a.png)
从运行结果可以看出,此时并没有按照上面的流程先Coerce后Validate的顺序执行,这可能是WPF内部做了一些特殊的处理。当属性被改变时,首先会调用Validate来判断传入的value是否有效,如果无效就不继续后续操作。并且CoerceValue后面并没有运行ValidateValue,而是直接调用PropertyChanged。这是因为CoerceValue操作并没有强制改变属性的值,而前面对这个值已经验证过了,所以也就没有必要再运行Valudate方法来进行验证了。但是如果在Coerce中改变了Value的值,那么还会再次调用Valudate操作来验证值是否合法。
## 2.7 依赖属性的监听
我们可以用两种方法对依赖属性的改变进行监听。这两种方法是:
* 使用[DependencyPropertyDescriptor](http://msdn.microsoft.com/zh-cn/library/system.componentmodel.dependencypropertydescriptor(v=vs.110).aspx)类
* 使用[OverrideMetadata](http://msdn.microsoft.com/zh-cn/library/ms597491(v=vs.110).aspx)的方式。
下面分别使用这两种方式来实现下对依赖属性的监听。
第一种方式:定义一个派生于依赖属性所在的类,然后重写依赖属性的元数据并传递一个PropertyChangedCallback参数即可,具体的实现如下代码所示:
```
1 public class MyTextBox : TextBox
2 {
3 public MyTextBox()
4 : base()
5 {
6 }
7
8 static MyTextBox()
9 {
10 //第一种方法,通过OverrideMetadata
11 TextProperty.OverrideMetadata(typeof(MyTextBox), new FrameworkPropertyMetadata(new PropertyChangedCallback(TextPropertyChanged)));
12 }
13
14 private static void TextPropertyChanged(DependencyObject sender, DependencyPropertyChangedEventArgs args)
15 {
16 MessageBox.Show("", "Changed");
17 }
18 }
```
第二种方法:这个方法更加简单,获取DependencyPropertyDescriptor并调用AddValueChange方法为其绑定一个回调函数。具体实现代码如下所示:
```
public MainWindow()
{
InitializeComponent();
//第二种方法,通过OverrideMetadata
DependencyPropertyDescriptor descriptor = DependencyPropertyDescriptor.FromProperty(TextBox.TextProperty, typeof(TextBox));
descriptor.AddValueChanged(tbxEditMe, tbxEditMe_TextChanged);
}
private void tbxEditMe_TextChanged(object sender, EventArgs e)
{
MessageBox.Show("", "Changed");
}
```
## 三、总结
到这里,依赖属性的介绍就结束了。WPF中的依赖属性通过一个静态只读字段进行定义,并且在静态构造函数中进行注册,最后通过.NET传统属性进行包装,使其使用与传统的.NET属性并无两样。在后面一篇文章将分享WPF中新的事件机制——路由事件。
本文所有源码下载:[DependencyPropertyDemo.zip](http://files.cnblogs.com/zhili/DependencyPropertyDemo.zip)
- 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 领域驱动设计实战系列总结