无论是 WPF,还是 UWP,只要你用了绑定或者标记扩展,一定会碰到一个神奇的值——DependencyProperty.UnsetValueUnsetValue 是什么意思?为什么会出现这个值呢?如果要让 UnsetValue 为我们所用,正确的用法又是什么呢?


DependencyProperty.UnsetValue 是什么?

要知道这是什么,一定要看源码:

/// <summary> Standard unset value </summary>
public static readonly object UnsetValue = new NamedObject("DependencyProperty.UnsetValue");

这是一个 NamedObject,而 NamedObject 又是什么呢?

internal class NamedObject
{
public NamedObject(string name)
{
if (String.IsNullOrEmpty(name)) throw new ArgumentNullException(name);
_name = name;
}
public override string ToString()
{
if (_name[0] != '{') _name = String.Format(CultureInfo.InvariantCulture, "{{{0}}}", _name);
return _name;
}
string _name;
}

好吧,其实这个类根本就没有什么用途,微软只是随便找了一个类,以便你在 Visual Studio 调试器或者你自己用代码输出值的时候能够显示一个预设好的字符串。真的只是起调试作用的啊!

DependencyProperty.UnsetValue 的定义中,只是为了让大家调试的时候显示 DependencyProperty.UnsetValue 而已。值本身不代表任何意义,只是为了说明遇到了一个“未设置”的值。

但是有人会问:null 在调试的时候也会显示 null 啊,为啥不用 null,要特别准备一个值呢?

这是因为在绑定中,null 可能是一个合理的值,可能会被故意用在绑定中来达到某种目的。于是微软必须用一个大家平常开发中一定不会用到的值来表示“不合理”,于是祭出了 DependencyProperty.UnsetValue

什么情况下会出现 DependencyProperty.UnsetValue?

正常情况下,只有以下两处代码会遇到 DependencyProperty.UnsetValue

  1. 在用于绑定的转换器 IValueConverter IMultiValueConverter 的代码里面;
  2. 在 XAML 标记扩展 MarkupExtension 里面。

而以上两处代码,只有在发生以下三种情况时才会遇到 DependencyProperty.UnsetValue

  1. 绑定出现了错误,也就是说绑定从最开始的源值到目标值的若干次转换过程中任何阶段发生了错误以至于无法成功转换到目标值。
    虽然我们写的是一个 {Binding XXX},但 XXX 可能由另外的绑定来提供(例如逻辑父控件的 DataContext)。一次次绑定的源值是上一个绑定的目标值,于是这样的关系组合成一个绑定提供值的链条。链条中只要有一处不能提供合理的值,就会在绑定中得到 UnsetValue
  2. 绑定或者标记扩展写在了 ControlTemplate 或者 DataTemplate 里面,但此时并没有指定数据源。
    在模板应用到实际的控件之前,模板本身也会执行一次 BindingMarkupExtension 的逻辑。于是如果绑定需要依赖于实际的控件,那么实际上 BindingMarkupExtension 会至少执行两次,其中第一次便是模板中的那一次。此时获取依赖属性的值时拿到的便是 DependencyProperty.UnsetValue
  3. 使用依赖项属性的 ReadLocalValue 来获取值,而不是 GetValue;但此时并没有为依赖对象设置值。
    如果没有设置值,那么 GetValue 会返回更低优先级的值,一般情况下是依赖项属性在注册时的默认值;但 ReadLocalValue 就是在获取显式设置的那个值,如果没设,就只能是 DependencyProperty.UnsetValue 了。

我们应该如何正确使用 DependencyProperty.UnsetValue?

微软官方对于 DependencyProperty.UnsetValue 的介绍,专门的文档中只有一个说法,就是用来表示“不合理”的值,却并没有说明什么情况下为合理,什么情况下为不合理。但好在微软将一些推荐写法散落在了多个不同的文章中。这里整理在一起,以便为大家对 DependencyProperty.UnsetValue 的正确使用提供指导。

  1. 在注册依赖项属性的时候,不要使用 DependencyProperty.UnsetValue 作为默认值。
    这个值本意其实并不是在说“未设置”,而是代表“不合理”。默认值必须是“合理地”才行。微软官方文档 Custom dependency properties 对此的解释是,如果默认值设置为 UnsetValue,则会在大家使用其值的时候产生混淆,并不能区分到底是依赖属性(的绑定系统)提供值的时候出错了还是因为只是默认没设置。
  2. 在写绑定的转换器的时候,如果转换有错误,不应该抛出异常,而是应该返回一个 DependencyProperty.UnsetValue,以便阻止绑定中继续传递值。
    微软在 Data binding in depth 中写出了这个要求,而在 How to: Convert Bound Data 中给出了示例代码。
  3. 如果需要在 CoerceValueCallback 回调中验证值的合理性,当值不合理的时候,返回 DependencyProperty.UnsetValue
    这将告诉依赖属性系统阻止这次值的更改。

参考资料

DependencyProperty.UnsetValue 的正确打开方式的更多相关文章

  1. iOS开发小技巧--相机相册的正确打开方式

    iOS相机相册的正确打开方式- UIImagePickerController 通过指定sourceType来实现打开相册还是相机 UIImagePickerControllerSourceTypeP ...

  2. Xcode 的正确打开方式——Debugging(转载)

    Xcode 的正确打开方式——Debugging   程序员日常开发中有大量时间都会花费在 debug 上,从事 iOS 开发不可避免地需要使用 Xcode.这篇博客就主要介绍了 Xcode 中几种能 ...

  3. C#语法——泛型的多种应用 C#语法——await与async的正确打开方式 C#线程安全使用(五) C#语法——元组类型 好好耕耘 redis和memcached的区别

    C#语法——泛型的多种应用   本篇文章主要介绍泛型的应用. 泛型是.NET Framework 2.0 版类库就已经提供的语法,主要用于提高代码的可重用性.类型安全性和效率. 泛型的定义 下面定义了 ...

  4. InnoDB缓冲池预加载在MySQL 5.7中的正确打开方式

    InnoDB缓冲池预加载在MySQL 5.7中的正确打开方式 https://mp.weixin.qq.com/s/HGa_90XvC22anabiBF8AbQ 在这篇文章里,我将讨论在MySQL 5 ...

  5. Console控制台的正确打开方式

    Console控制台的正确打开方式 console对象提供了访问浏览器调试模式的信息到控制台 -- Console对象 |-- assert() 如果第一个参数断言为false,则在控制台输出错误信息 ...

  6. 任务队列和异步接口的正确打开方式(.NET Core版本)

    任务队列和异步接口的正确打开方式 什么是异步接口? Asynchronous Operations Certain types of operations might require processi ...

  7. (一)Redis for Windows正确打开方式

    目录 (一)Redis for Windows正确打开方式 (二)Redis for 阿里云公网连接 (三)Redis for StackExchange.Redis 下载地址 官网.中文网1 及 中 ...

  8. List的remove()方法的三种正确打开方式

    转: java编程:List的remove()方法的三种正确打开方式! 2018年08月12日 16:26:13 Aries9986 阅读数 2728更多 分类专栏: leetcode刷题   版权声 ...

  9. C++11随机数的正确打开方式

    C++11随机数的正确打开方式 在C++11之前,现有的随机数函数都存在一个问题:在利用循环多次获取随机数时,如果程序运行过快或者使用了多线程等方法,srand((unsigned)time(null ...

随机推荐

  1. 学习webpack3.x过程中遇到的问题:webpack-dev-server

    这篇博客主要记录的是本人在学习webpack3.x的过程中遇到的问题(虽然这几天4.0刚出来,但是我还是先学一下3.x吧) 1.配置文件可以用webpack启服务和热更新,步骤如下: ① 先下载:we ...

  2. 【Bitset】重识

    ---------------------------------------------------------------------------- 一题题目: 一题题解: 这个题目哪来入门再好不 ...

  3. ADC和RTC的寄存器的读取

    ADC的寄存器读取,int adc_read(void){ int result; #if ADSTART==0 result = ADC.ADCDAT0&0x3ff; while(!(ADC ...

  4. Android系统源代码

    Android系统源代码 在线源码网站 1,http://androidxref.com 2,http://www.grepcode.com/ 3,http://www.androidos.net.c ...

  5. 【程序员笔试面试必会——排序①】Python实现 冒泡排序、选择排序、插入排序、归并排序、快速排序、堆排序、希尔排序

    最近在准备笔试题和面试题,把学到的东西整理出来,一来是给自己留个笔记,二来是帮助大家学习. 题目: 给定一个int数组A及数组的大小n,请返回排序后的数组. 测试样例:  输入:[1,2,3,5,2, ...

  6. Dapper 条件语句(Where) 中参数使用

    public static List<ECInput> GetECInputList(DateTime beginDate,DateTime endDate,string[] barcod ...

  7. C# 获取命名空间对应的程序集位置

    由于同名命名空间会被多个程序集使用,C#没有提供直接的方法(对象浏览器也不行)通过命名空间获得程序集位置,这样就不方便找到那些引用文件时什么. 那么可以在立即窗口,中断某个代码的时候,去查询类所在程序 ...

  8. 《深入理解mybatis原理3》 Mybatis数据源与连接池

    <深入理解mybatis原理> Mybatis数据源与连接池 对于ORM框架而言,数据源的组织是一个非常重要的一部分,这直接影响到框架的性能问题.本文将通过对MyBatis框架的数据源结构 ...

  9. 1-22-shell脚本的基础

    1.1 shell 脚本的编写规范 1.2 变量与特殊变量应用 1.3局部变量与全局变量 1.4 条件测试表达式 ------------------------------------------- ...

  10. Ansible 手册系列 一(介绍)

    一 介绍 Ansible 是一个配置管理和应用部署工具,功能类似于目前业界的配置管理工具 Chef,Puppet,Saltstack.Ansible 是通过 Python 语言开发.Ansible 平 ...