今天有朋友写Silverlight代码遇到一个问题,让我一起看一下。这是他写的一个测试类:

class Foo : DependencyObject
{public List<int> Bars{get { return (List<int>)GetValue (BarsProperty); }set { SetValue (BarsProperty, value); }}public static readonly DependencyProperty BarsProperty =DependencyProperty.Register ("Bars",typeof (List<int>),typeof (Foo),new PropertyMetadata (new List<int> ()));
}

使用代码如下:

可见,当foo2刚刚创建时,Bars中已经有一个元素了。

SL/WPF达人们看到这里肯定已经笑了,我刚开始学习DependencyProperty时也曾在这个问题上卡住。

在Immediate Window中测试foo1与foo2的Bars属性:

果然,Bars其实是同一个List<int>对象。

朋友说,我上网查了,已经知道像List这些集合类属性不能用DependencyProperty Metadata的defaultValue来初始化,必须要在类的构造器中初始化(参考http://msdn.microsoft.com/zh-cn/library/cc903961(v=VS.95).aspx):

class Foo : DependencyObject
{public List<int> Bars{get { return (List<int>)GetValue (BarsProperty); }set { SetValue (BarsProperty, value); }}public static readonly DependencyProperty BarsProperty =DependencyProperty.Register ("Bars",typeof (List<int>),typeof (Foo),new PropertyMetadata (null));public Foo (){Bars = new List<int> ();}
}

这样就能得到正常结果。但他不明白的是,为什么微软不为这种情况特别处理一下,而要留给开发人员这么一个陷阱?

说实话,我当时被问住了,因为我之前也只是机械地把结论当做一个开发的“注意事项”记了下来,至于原因,并没有深思。

---------------------

其实,从原理上思考一下,并不难得出结论。微软并不是不愿意处理,而是无法处理。

首先,这个问题并不是只有集合类型值才会遇到,所有的引用类型都可能遇到此问题,参考下面的示例:

class Foo : DependencyObject
{public List<int> Bars{get { return (List<int>)GetValue (BarsProperty); }set { SetValue (BarsProperty, value); }}public static readonly DependencyProperty BarsProperty =DependencyProperty.Register ("Bars",typeof (List<int>),typeof (Foo),new PropertyMetadata (new List<int> ()));public int Length{get { return (int)GetValue (LengthProperty); }set { SetValue (LengthProperty, value); }}public static readonly DependencyProperty LengthProperty =DependencyProperty.Register ("Length",typeof (int),typeof (Foo),new PropertyMetadata (5));public class Data{public int Value { get; set; }}public Data MyData{get { return (Data)GetValue (MyDataProperty); }set { SetValue (MyDataProperty, value); }}public static readonly DependencyProperty MyDataProperty =DependencyProperty.Register ("MyData",typeof (Data),typeof (Foo),new PropertyMetadata (new Data () { Value = 3 }));
}

该类声明了三个DP:List<int>类型的Bars,int类型的Length和自定义类Data类型的MyData,均用DP的defaultValue进行初始化。调试结果如下:

可见,在foo2刚刚构造完成时,Bars和MyData属性都已经和foo1一致了,使用object.ReferenceEquals比较后证明确实均为同一实例。这也是可以料想到的结果,所谓的“集合”与其他的引用类型相比,并没有什么本质的特殊性。至于为何一般此问题讨论的都是集合的特殊性,以及MSDN上也特别说明是“集合类型”,我想可能是因为DependencyProperty常用的是一些基本的值类型,String虽是引用类型,却是Immutable的,因此也不会出现这个问题;那么要用到引用类型而又可能出问题的,最常见的就是集合类型了,因此MSDN要特别把这个问题拿出来说明一下。

---------------------

好,那现在问题变得一般化了:一般的引用类型,为何会存在此“陷阱”?

我们还需要从DP的原理说起。DP必须定义为静态成员,其本质是静态的哈希表(当然其实际实现要复杂得多,至于为什么这么实现,微软有很多这方面的介绍,比如可以支持资源、样式、动画、数据绑定等等)。而类的每个实例在这张静态表中就有一项,用来记录每个实例对应DP的值,这个值会使用DP的PropertyMetadata所指定的defaultValue进行初始化。我们使用SetValue和GetValue方法,其实就是在对这张表中的对应项进行读写。

说到这里,大家应该都已经明白问题所在了,值类型属性的值就是存储在哈希表中的,因此修改值类型属性不会影响其他实例的相同属性;而引用类型属性在哈希表中存储的只不过是一个引用,初始化为指向同一对象,当对该属性进行修改时(比如向集合中添加元素),其实是向所有实例所共享的对象中添加元素,因此,其他实例的属性也会受到影响。

---------------------

也许还有人会问,为什么微软不为每个实例初始化一个新的引用对象呢?不错,我也这么想过,但正如我前面所说,按照现在的设计,微软不一定是没有想到,很可能是做不到。实际上,在PropertyMetadata对象初始化之前,引用对象已经创建完成了:

new PropertyMetadata (new List<int> ())

思考一下这段代码的运行顺序,在调用PropertyMetadata的构造函数之前,List<int>对象已经构造完成,因此PropertyMetadata拿到的就只是一个引用,它无法知道如何去构造这个引用对象,因此无法为哈希表每项都创建一个新的实例,而只能老老实实地使用手里这个引用。

因此,对于初始值为引用类型的DependencyProperty来说,我们确实只能在类的构造方法中对齐初始化(虽然微软建议使用PropertyMetadata初始化)。

Bars = new List<int> ();

等等,别以为这就完了,这里使用了Bars属性,实际上是调用了SetValue方法。那对于readonly的DependencyProperty呢?事实上,在使用集合时,很明显这个属性本身最好是只读的,我们修改的是集合的元素,而不是集合这个属性本身。在WPF中,定义一个只读的DependencyProperty的方法可以参考这里(SL 4不支持RegisterReadOnly方法,可以自己实现ReadOnly的效果,参考这篇博客),代码如下:

public List<int> Bars
{get { return (List<int>)GetValue (barsPropertyKey.DependencyProperty); }
}private static readonly DependencyPropertyKey barsPropertyKey =DependencyProperty.RegisterReadOnly ("Bars",typeof (List<int>),typeof (Foo),new PropertyMetadata (null));

此时,使用了私有的DependencyPropertyKey类型字段代替了原来公开的DependencyProperty类型字段,Bars属性也去掉了set方法。那要如何进行初始化呢?

这时,SetValue方法的另一个重载就用上了,这个重载接受一个DependencyPropertyKey而不是一个DependencyProperty,而这正是RegisterOnly方法的返回值。(我原来一直奇怪为什么GetValue只有一个版本而SetValue有两个版本…)

代码如下:

public Foo ()
{SetValue (barsPropertyKey, new List<int> ());
}

---------------------

现在回到原来的问题上。

对于这样一个“陷阱”,难道真的没有办法?难道必须小心地把初始化放到类实例的构造函数中?

在Prism框架中,当我们向一个Region注册View时,可以使用IRegionManager的扩展方法RegisterViewWithRegion:

public static IRegionManager RegisterViewWithRegion (this IRegionManager regionManager, string regionName, Func<object> getContentDelegate);

第三个参数,本应传入要注册的View对象,却传入了一个Func<object>,这是什么意思?我们再看一下实际使用代码:

_regionManager.RegisterViewWithRegion (RegionNames.SidebarRegion,() => _container.Resolve<ISidebarPresenter> ().View);

可见,实际传入的是一个匿名委托,这个委托告诉了RegionManager如何创建一个View。RegionManager得到这个委托后,用一个Dictinoary把它保存起来,到实际创建View的时候才去调用。因此_container.Resolve<ISidebarPresenter> ().View这段代码要到实际创建View时才会运行。同时,RegionManager只需反复调用委托,也具备了重复创建多个View实例的能力。

---------------------

这样的方法,也许WPF/SL会借鉴一下?或许某一天,我们可以写出这样的代码:

class Foo : DependencyObject
{public List<int> Bars{get { return (List<int>)GetValue (BarsProperty); }set { SetValue (BarsProperty, value); }}public static readonly DependencyProperty BarsProperty =DependencyProperty.Register ("Bars",typeof (List<int>),typeof (Foo),new PropertyMetadata (() => new List<int> ()));
}

而不用再去担心这样的“陷阱”。

转载于:https://www.cnblogs.com/Gildor/archive/2010/05/24/1742743.html

Silverlight/WPF中DependencyProperty使用陷阱一枚相关推荐

  1. Silverlight与WPF中BeginInvoke的差异

    Silverlight/WPF中,如果要在多线程中对界面控件值做修改,用Dispatcher对象的BeginInvoke方法无疑是最方便的办法 ,见:温故而知新:WinForm/Silverlight ...

  2. 分享Silverlight/WPF/Windows Phone一周学习导读(06月06日-06月11日)

    Windows 8预览版推出后,Silverlight社区掀起一番新的"Silverlight灭亡"讨论,由于Windows 8预览版中微软重点强调HTML 5和Javascrip ...

  3. 在Silverlight 和WPF中使用预编译指令 if..else..endif (译)

    下面的是中文翻译,有些扯淡的话就略过了,想看原文,请到这里 对于预编译指令,作者分成了几篇文章来讲解的.鉴于翻译后的文字较少,我把作者的几篇合为了一篇.下面进入正题. 一.总揽: 想要编写特定平台的代 ...

  4. 分享Silverlight/WPF/Windows Phone一周学习导读(10月30日-11月6日)

    分享Silverlight/WPF/Windows Phone一周学习导读(10月30日-11月6日) 本周Silverlight学习资源更新 Silverlight 定位 niejunhua [学习 ...

  5. 分享Silverlight/WPF/Windows Phone一周学习导读(4月4日-4月9日)

    期待已久的Silverlight 5即将到来,上周不少关于MIX11的话题,其中值得关注的有以下几篇: MIX11: Silverlight 5,Windows Phone,IE9,HTML5及直播预 ...

  6. 分享Silverlight/WPF/Windows Phone一周学习导读(07月25日-07月31日)

    上周,微软发布Visual Studio LightSwitch 2011正式版,为商业应用开发人员推出一个新的应用开发产品.Visual Studio LightSwitch 2011 作为一种简单 ...

  7. 分享Silverlight/WPF/Windows Phone一周学习导读(07月18日-07月24日)

    上周,微软推出Silverlight新版官方网站,新网站综合旧版网站内容,并增加更多Silverlight学习资源以及案例展示,Silverlight官网是学习Silverlight开发技术的主要资源 ...

  8. 谈谈Silverlight 2中的视觉状态管理 Part1

    概述 在WPF和Silverlight中的控件模板支持自定义控件的观感,所谓的外观,指控件的视觉效果:而感觉则是控件交互的响应性,如在控件上按下鼠标.控件获得焦点等状态的改变.微软在Silverlig ...

  9. 分享Silverlight/WPF/Windows Phone一周学习导读(1月9日-1月16日)

    分享Silverlight/WPF/Windows Phone上周学习导读. 本周Silverlight学习资源更新: Prism 4学习笔记之Module Jason Li WPF/Silverli ...

最新文章

  1. 【puthon】把大量csv文件写入h5文件制作数据集
  2. [AI开发]目标跟踪之行为分析
  3. 闪退没由报错_使命召唤:(cod16)出现的闪退问题以及解决办法
  4. spring注解记录
  5. Harmony OS — Switch开关状态
  6. [python]设计模式
  7. 070002 《设计心理学》读后感
  8. 机票预订系统的数据流程图及实体联系图
  9. EasyReport报表工具
  10. 百度地图android版v6.7,百度CarLife
  11. android 桌面快捷方式静态shortcuts异常
  12. 相对路径与绝对路径的写法
  13. 10款超实用的程序员工具,工具用得好,头发掉的少
  14. 暗黑破坏神3 --野蛮人 防御装备选择篇
  15. php 图片处理羽化,ps中羽化是什么意思
  16. 嵌入式GUI及其开发工具——miniGUI和mStudio
  17. 山东青岛:青岛海科展让青岛这座“会展之都”更有“看头”
  18. 计算一年中的第多少天
  19. 人员车辆船舶定位与调度
  20. m分别使用BP神经网络和GRNN网络进行时间序列预测matlab仿真

热门文章

  1. C#-求int数组中连续偶数列的个数
  2. shell 常用命令
  3. 一文看懂Stacking!(含Python代码)
  4. 微信 小程序组件 焦点切换
  5. 027_编写MapReduce的模板类Mapper、Reducer和Driver
  6. Python基础学习笔记(十三)异常
  7. webpack4导入全局sass文件
  8. RN PickerView组件
  9. 五分钟教你使用vue-cli3创建项目(三种创建方式,小白入门必看)
  10. 《盘古开天地》的故事内容,《盘古开天地》的启示