简诉
- 不需要WPF就能理解依赖项属性(后文简称依赖属性)。
- 创建一个Console工程,从零开始演变。
- 本文有误之处希望大家不吝赐教,批评指正。
- 让我们开始吧,一起来重现这个简单而精湛的演变过程!
静态编译-强类型版本
- 创建一个Owner类并定义普通属性
- Owner指代任意实体,例如Button/Shape/Window等等
public class Owner
{
public int Id { get; set; }
public string Content { get; set; }
}
动态调用-内存态版本
- 将代码结构变为一个Dictionary即可将属性从编译态变为内存态。
- 例如 public string Content { get; set; } 变为 dic.Add("Content", "...")
public class Owner
{
public Dictionary dic = new Dictionary();
}
静态+动态版本
- 强类型有很多好处,可以避免字符串难以记忆、编译时类型检测等各种问题,网上的文章很多,这里不再赘述。
- 在保留"内存态"的同时,使用"静态强类型属性"作为调用入口,桥接(转发)到内存态属性。
public class Owner
{
public Dictionary dic = new Dictionary();
public int Id
{
get { return (int)dic["Id"]; }
set { if (!dic.TryAdd("Id", value)) { dic["Id"] = value; } }
}
public string Content
{
get { return (string)dic["Content"]; }
set { if (!dic.TryAdd("Content", value)) { dic["Content"] = value; } }
}
}
抽象出DependencyObject
- 将Owner的功能提取到基类中,使代码可以支持不同的类,这个基类就是DependencyObject。
- Owner继承DependencyObject,从而获得"属性容器"的能力。
public class DependencyObject
{
private Dictionary dic = new Dictionary();
public object GetValue(string propertyName)
{
return dic[propertyName];
}
public void SetValue(string propertyName, object value)
{
if (!dic.TryAdd(propertyName, value)) { dic[propertyName] = value; }
}
}
public class Owner : DependencyObject
{
public int Id
{
get { return (int)GetValue("Id"); }
set { SetValue("Id", value); }
}
public string Content
{
get { return (string)GetValue("Content"); }
set { SetValue("Content", value); }
}
}
抽象出DependencyProperty
- 属性的信息不止"属性名"这一项,应该包括{属性名,属性类型,属性拥有者类型,属性值,属性关联数据,其他关联数据}
- 其中"属性值"是和实例相关。这很好理解,不同Owner实例的Id属性值各不相同。
- 除"属性值"之外的所有属性信息和类相关,也就是说全局只需要一份。把这一份信息提取出来抽象为一个对象就叫DependencyProperty。
- 一个DependencyProperty描述一项属性的信息,N个DependencyProperty才能描述N项属性的信息。
- N个DependencyProperty聚合为索引列表,并被Owner包含。索引列表全局只有一份,因此它也被全局所包含。
using System;
using System.Collections.Generic;
public class DependencyObject
{
private Dictionary dic = new Dictionary();
public object GetValue(DependencyProperty dp)
{
return dic[dp.PropertyName];
}
public void SetValue(DependencyProperty dp, object value)
{
if (!dic.TryAdd(dp.PropertyName, value)) { dic[dp.PropertyName] = value; }
}
}
public class DependencyProperty
{
private DependencyProperty() { }
public static DependencyProperty Register(string propertyName, Type propertyType, Type ownerType)
{
DependencyProperty dp = new DependencyProperty();
dp._propertyName = propertyName;
dp._propertyType = propertyType;
dp._ownerType = ownerType;
return dp;
}
private string _propertyName;
public string PropertyName { get { return _propertyName; } }
private Type _propertyType;
public Type PropertyType { get { return _propertyType; } }
private Type _ownerType;
public Type OwnerType { get { return _ownerType; } }
}
public class Owner : DependencyObject
{
public static readonly DependencyProperty IdProperty;
public static readonly DependencyProperty ContentProperty;
public int Id { get { return (int)GetValue(IdProperty); } set { SetValue(IdProperty, value); } }
public string Content { get { return (string)GetValue(ContentProperty); } set { SetValue(ContentProperty, value); } }
static Owner()
{
IdProperty = DependencyProperty.Register("Id", typeof(int), typeof(Owner));
ContentProperty = DependencyProperty.Register("Content", typeof(string), typeof(Owner));
}
}
public class Program
{
static void Main(string[] args)
{
//赋值Owner1
Owner o1 = new Owner();
o1.Id = 1;
o1.Content = "lqy";
//赋值Owner2
Owner o2 = new Owner();
o2.Id = 2;
o2.Content = "lcl";
//访问
Console.WriteLine(o1.Content);
Console.WriteLine(o2.Content);
}
}
总结
- Owner的实例包含独立的属性字典。
- Owner类包含静态属性索引表,索引表提供属性字典的键,该键需要关联到具体的Owner实例才能从属性字典中拿到值。
- 属性索引表是一个抽象概念,不是具体对象,它表示多项静态DependencyProperty的聚合。
- 属性索引表既然是静态的,那么它既属于Owner,也属于全局。
后记
- 至此,从数据结构的角度来讲,可以理解为什么static的DependencyProperty可以拥有实例数据。
- 从功能的角度来讲,还需要一些补充。微软劫持了GetValue/SetValue函数,将WPF的完美架构插入到其中,从而提供相关功能。
- 既然代码运行权被框架劫持了,转到了框架的内部,那可以在里面做任何事情。
- (发散和吐槽)可以在函数里面抽支烟、跳支舞什么的
- (发散和吐槽)输出一个HelloWorld
- (发散和吐槽)检测甚至统计数据
- (发散和吐槽)继续调用GetValue或SetValue,递归下去。当然啦,必须存在递归终止条件。
- (发散和吐槽)发送一些事件,把观察者模式、命令模式什么的都用上。事件的回调函数中可以继续调用自己或其他对象的GetValue或SetValue,程序结构上造成隔层递归,功能结构上则实现了"变化的传递",从而达到"绝对的数据一致性"。
- (发散和吐槽)插入更多的功能,将功能的实例数据存放到DependencyObject中,全局唯一数据存储到DependencyProperty中。从面向对象的思想来讲只要能拿到实例上下文和全局上下文就能实现Everything。
- 因此,不应该割裂的去理解"依赖项属性",它利用面向对象思想构造了一种程序组织结构,是一个整体。