在WPF中,(自定义)依赖属性和附加属性有什么不同?每一种都有什么用处呢?这两种实现通常有什么不同?

推荐答案

摘要

因为我几乎找不到关于这个问题的文件,所以我花了一些时间在source code个左右,但这里有一个答案.

将依赖项属性注册为常规属性和附加属性(而不是"哲学"属性)之间存在差异(regular properties are intended to be used by the declaring type and its deriving types, attached properties are intended to be used as extensions on arbitrary DependencyObject instances)."哲学的",因为正如@MarqueIV在对@ReedCopsey答案的 comments 中所注意到的,正则属性也可以用于任意DependencyObject个实例.

此外,我不同意其他答案,即附加属性是"依赖属性类型",因为这是误导性的——没有任何"类型"的依赖属性.该框架并不关心该财产是否被登记为附属财产——甚至无法确定(从某种意义上说,这些信息没有被记录,因为它们无关紧要).事实上,所有属性都像附加属性一样进行注册,但对于常规属性,会执行一些额外的操作,稍微修改它们的行为.

代码节选

为了省go 你自己查看源代码的麻烦,这里有一个简化版本.

注册未指定元数据的属性时,调用

DependencyProperty.Register(
    name: "MyProperty",
    propertyType: typeof(object),
    ownerType: typeof(MyClass))

调用时产生exactly the same个结果

DependencyProperty.RegisterAttached(
    name: "MyProperty",
    propertyType: typeof(object),
    ownerType: typeof(MyClass))

但是,在指定元数据时

DependencyProperty.Register(
    name: "MyProperty",
    propertyType: typeof(object),
    ownerType: typeof(MyClass),
    typeMetadata: new FrameworkPropertyMetadata
    {
        CoerceValueCallback = CoerceCallback,
        DefaultValue = "default value",
        PropertyChangedCallback = ChangedCallback
    });

相当于打电话

var property = DependencyProperty.RegisterAttached(
    name: "MyProperty",
    propertyType: typeof(object),
    ownerType: typeof(MyClass),
    defaultMetadata: new PropertyMetadata
    {
        DefaultValue = "default value",
    });
property.OverrideMetadata(
    forType: typeof(MyClass),
    typeMetadata: new FrameworkPropertyMetadata
    {
        CoerceValueCallback = CoerceCallback,
        DefaultValue = "default value",
        PropertyChangedCallback = ChangedCallback
    });

结论

常规依赖项属性和附加依赖项属性之间的关键(也是唯一)区别是通过DependencyProperty.DefaultMetadata属性提供的默认元数据.这甚至在Remarks节中提到:

对于非附加属性,此属性返回的元数据类型不能强制转换为PropertyMetadata类型的派生类型,即使该属性最初是用派生元数据类型注册的.如果希望原始注册的元数据包括其原始可能派生的元数据类型,请调用GetMetadata(Type),将原始注册类型作为参数传递.

对于附加属性,此属性返回的元数据类型将与原始RegisterAttached注册方法中给出的类型匹配.

这在提供的代码中清晰可见.注册方法中也隐藏了一些提示,即对于RegisterAttached,元数据参数名为defaultMetadata,而对于Register,元数据参数名为typeMetadata.对于附加属性,提供的元数据将成为默认元数据.但是,对于常规属性,默认元数据始终是PropertyMetadata的新实例,只设置了DefaultValue(来自提供的元数据或自动设置).只有对OverrideMetadata的后续调用实际使用了提供的元数据.

后果

主要的实际区别是,对于常规属性,CoerceValueCallbackPropertyChangedCallback适用于only种类型,这些类型源自声明为所有者类型的类型,而对于附属属性,它们适用于all种类型.例如,在这种情况下:

var d = new DependencyObject();
d.SetValue(SomeClass.SomeProperty, "some value");

如果该财产登记为附属财产,则登记为PropertyChangedCallback will be called;如果该财产登记为普通财产,则登记为will not be called.CoerceValueCallback也一样.

第二个区别源于这样一个事实:OverrideMetadata要求提供的类型派生自DependencyObject.实际上,这意味着常规属性的所有者类型可以是DependencyObject中的must derive,而中附加属性的所有者类型可以是any(包括静态类、 struct 、枚举、委托等).

增刊

除了@MarqueIV的建议之外,我还在几个场合遇到过这样的观点,即常规属性和附加属性在XAML中的使用方式不同.也就是说,常规属性需要隐式名称语法,而不是附加属性所需的显式名称语法.从技术上讲,这是not true,尽管实际上通常是这样.为清楚起见,请执行以下操作:

<!-- Implicit property name -->
<ns:SomeClass SomeProperty="some value" /> 

<!-- Explicit property name -->
<DependencyObject ns:SomeClass.SomeProperty="some value" />

pure XAML中,管理这些语法用法的唯一规则如下:

  • 隐式名称语法可用于元素if and only if,该元素表示的类具有该名称的CLR属性
  • 显式名称语法可用于元素if and only if.全名第一部分指定的类公开了相应的静态get/set方法(称为accessors),其名称与全名第二部分匹配

满足这些条件使您能够使用相应的语法,而不管支持依赖项属性是注册为常规属性还是附加属性.

现在,所提到的误解是由以下事实造成的:绝大多数教程(以及Visual Studio个代码片段)都指示您使用CLR属性作为常规依赖属性,而获取/设置附加属性的访问器.但是没有什么能阻止你同时使用这两种语言,允许你使用你喜欢的语法.

.net相关问答推荐

使用PowerShell在Windows容器内安装exe

使用React路由加载器获取数据不能正常工作

获取Ef-Core集合的DeleteBehavior

如何将 select 语句详细信息提取到不同的方法中仍然保持Eager 加载?

.gitignore 和 Visual Studio 项目:忽略 bin/Debug 目录但不忽略 bin/Release 目录

lock() 是否保证按请求的顺序获得?

如何让 DateTimePicker 显示一个空字符串?

app.config 文件和 XYZ.settings 文件有什么区别?

Automapper:使用 ReverseMap() 和 ForMember() 进行双向映射

如何在 WPF 中创建/制作圆角按钮?

什么是 SUT,它来自哪里?

ILookup 接口与 IDictionary

覆盖 ASP.NET MVC 中的授权属性

CryptographicException 未处理:系统找不到指定的文件

IEnumerable vs IReadonlyCollection vs ReadonlyCollection 用于公开列表成员

C# 相当于 Java 的 Exception.printStackTrace()?

任何人都知道缺少枚举通用约束的好方法吗?

使用 C# vs F# 或 F# vs C# 有什么好处?

如何从 webclient 获取状态码?

捕获控制台退出 C#