在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相关问答推荐

从Couchbase删除_txn文档的推荐方法?""

从窗体中移除另一个控件中引用的控件时获取设计时通知

WinForm Task.Wait :为什么它会阻塞 UI?

NuGet 兼容与计算框架(Xamarin 和 .NET 6)

为什么 .NET 中的 System.Version 定义为 Major.Minor.Build.Revision?

类似于字典但没有值的 C# 数据 struct

.Include() 与 .Load() 在 EntityFramework 中的性能

使用 Windows 服务和 C# 检测 USB 驱动器插入和移除

我应该默认推荐密封类吗?

如何在 EF 代码优先中禁用链接表的级联删除?

msbuild,定义条件编译符号

互锁且易变

在 WPF DataGrid 中绑定 ComboBoxColumn 的 ItemsSource

无法加载文件或程序集Antlr3.Runtime (1)或其依赖项之一

从流中获取 TextReader?

在 Windows 窗体 C# 应用程序中拥有配置文件的最简单方法

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

在 C#/.NET 中合并两个图像

Roslyn 编译代码失败

System.ServiceModel 在 .NET Core 项目中找不到