Dot-Net

WPF中的依賴屬性和附加屬性有什麼區別?

  • August 6, 2009

WPF 中的(自定義)依賴屬性和附加屬性有什麼區別?各有什麼用途?實現通常有何不同?

抽象的

由於我幾乎沒有找到關於此事的文件,因此需要對原始碼進行一些研究,但這裡有一個答案。

將依賴屬性註冊為正常屬性和附加屬性之間存在區別,而不是“哲學”屬性(正常屬性旨在由聲明類型及其派生類型使用,附加屬性旨在用作任意 DependencyObject 實例的擴展)。“哲學”,因為正如@MarqueIV 在他對@ReedCopsey 答案的評論中註意到的那樣,正常屬性也可以用於任意DependencyObject實例。

此外,我不得不不同意其他說附加屬性是“依賴屬性類型”的答案,因為它具有誤導性——沒有任何依賴屬性的“類型”。該框架並不關心該屬性是否已註冊為附加 - 甚至無法確定(從某種意義上說,該資訊沒有被記錄,因為它是不相關的)。事實上,所有屬性都像附加屬性一樣註冊,但在正常屬性的情況下,會做一些額外的事情來稍微修改它們的行為。

程式碼摘錄

為了省去您自己瀏覽原始碼的麻煩,這裡有一個簡單的版本。

註冊未指定元數據的屬性時,呼叫

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

產生與呼叫完全相同的結果

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屬性獲得的預設元數據。備註部分甚至提到了這一點:

對於非附加屬性,此屬性返回的元數據類型不能轉換為PropertyMetadata類型的派生類型,即使該屬性最初是使用派生元數據類型註冊的。如果您希望原始註冊的元數據包括其原始可能派生的元數據類型,請改為呼叫GetMetadata(Type),將原始註冊類型作為參數傳遞。

對於附加屬性,此屬性返回的元數據類型將與原始RegisterAttached註冊方法中給出的類型相匹配。

這在提供的程式碼中清晰可見。註冊方法中也隱藏了一些小提示,即對於RegisterAttached元數據參數是命名的defaultMetadata,而對於Register它是命名的typeMetadata。對於附加屬性,提供的元數據成為預設元數據。但是,在正常屬性的情況下,預設元數據始終是PropertyMetadataDefaultValue設置的新實例(來自提供的元數據或自動)。只有後續呼叫OverrideMetadata實際使用提供的元數據。

結果

主要的實際區別在於,對於正常屬性,CoerceValueCallbackandPropertyChangedCallback適用於從聲明為所有者類型的類型派生的類型,而對於附加屬性,它們適用於所有類型。例如在這種情況下:

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

如果該財產被註冊為附屬財產,則將呼叫註冊者,但PropertyChangedCallback 如果將其註冊為正常財產,**則不會呼叫該註冊者。**也一樣CoerceValueCallback

次要差異源於OverrideMetadata要求提供的類型派生自DependencyObject. 實際上,這意味著正常屬性的所有者類型必須派生DependencyObject,而附加屬性 in 可以是任何類型(包括靜態類、結構、枚舉、委託等)。

補充

除了@MarqueIV 的建議之外,我曾多次遇到過這樣的觀點,即正常屬性和附加屬性在XAML中的使用方式不同。即,正常屬性需要隱式名稱語法,而不是附加屬性所需的顯式名稱語法。這在技術上是不正確的,儘管在實踐中通常是這樣。為了清楚起見:

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

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

純 XAML中,管理這些語法使用的唯一規則如下:

  • 當且僅當該元素表示的類具有該名稱的CLR屬性時,才能對元素使用隱式名稱語法
  • 當且僅當全名的第一部分指定的類公開適當的靜態get / set方法(稱為訪問)且名稱與全名的第二部分匹配時,才能在元素上使用顯式名稱語法

滿足這些條件使您能夠使用相應的語法,而不管支持依賴屬性是註冊為正常還是附加。

現在提到的誤解是由於絕大多數教程(以及庫存的Visual Studio程式碼片段)指示您將CLR屬性用於正常依賴項屬性,並使用獲取/設置訪問器來獲取附加屬性。但是沒有什麼能阻止您同時使用這兩種語法,讓您可以使用您喜歡的任何語法。

附加屬性是一種依賴屬性。不同之處在於它們的使用方式。

使用附加屬性,該屬性是在一個與其使用的類不同的類上定義的。這通常用於佈局。很好的例子是 Panel.ZIndex 或 Grid.Row - 您將其應用於控制項(即:Button),但它實際上是在 Panel 或 Grid 中定義的。該屬性“附加”到按鈕的實例。

例如,這允許容器創建可用於任何 UI 元素的屬性。

至於實現差異 - 基本上只是在定義屬性時使用 Register 與 RegisterAttached 的問題。

引用自:https://stackoverflow.com/questions/1240699