无论是 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. 关于JBoss的一些项目配置

    1. 如何使用 IP:port 的形式访问项目 : [1] 在standalone.xml文件中,查找<interfaces>标签,添加如下节点 : <interface name= ...

  2. mac 下如何建立vue-cli项目

    command+space : terminal //检测是否存在node node -v //检测是否带有npm安装管理工具 npm -v //具备进行下一步 //在documnets下建立vue文 ...

  3. JS组件系列——基于Bootstrap Ace模板的菜单和Tab页效果

    Ace模板地址:http://code.google.com/p/ace-engine/wiki/AceTemplate(有时会打不开) Ace英文官网:http://wrapbootstrap.co ...

  4. [PostgreSQL]PostgreSQL数据类型格式化函数——字符串和数值间的转换

    详情见官网:http://www.postgres.cn/docs/10/functions-formatting.html PostgreSQL中有以下格式化函数: 函数 返回类型 描述 例子 to ...

  5. bzoj1001平面图最小割转对偶图最短路

    https://www.lydsy.com/JudgeOnline/problem.php?id=1001 很明显的求对偶图的最短路即可(由于特判写错了一直wa = = ) //#pragma com ...

  6. 总结网站Mysql优化

    Mysql存储引擎 选择合适的存储引擎Innodb  myisam myisam: 写入数据非常快,适合使用场合dedecms/phpcms/discuz/微博系统等写入.读取操作多的系统. inno ...

  7. 1-16-2 LVM管理和ssm存储管理器使用&磁盘配额

    ssm存储管理器使用&磁盘配额 ssm存储管理器使用 系统存储管理器的使用 系统存储管理器(又称ssm,即system-storage-manager),是RHEL7/CentOS7新增的功能 ...

  8. 为红米Note 5 Pro编译Lineage OS 15.1的各种坑

    安装了ubuntu虚拟机,直接上网repo sync,网速特别慢,中间断了好多次,记得是3天吧,总算是下载成功了.中途还在淘宝上买过付费的VPN代理软件,有时候会打开代理来尝试,也是不太稳定.好歹第1 ...

  9. shell---正则表达式和文本处理器

    -----正则表达式----- grep -n  :显示行号 -o  :只显示匹配的内容 -q  :静默模式,没有任何输出,得用$?来判断执行成功没有,即有没有过滤到想要的内容 -l  :如果匹配成功 ...

  10. 【转】Fork/Join框架测试

    Fork/Join框架介绍 下面使用该框架计算0-50000000000的和,并比较普通计算方法.Fork/Join框架.Java8新特性三种计算方式的计算时间: import java.time.D ...