【问题标题】:Demystifying Dependency Properties揭秘依赖属性
【发布时间】:2011-05-19 19:53:16
【问题描述】:

我在 SO 和其他网站上阅读了很多关于依赖属性的信息。但是,真的没有找到一个很好的解释,仍然很困惑。我同时使用 SL 和 WPF。就实现而言,它们在 SL 和 WPF 中是否有所不同?为什么我们真的需要它们?它们是静态的是否意味着它们的价值观是共享的? MS 引入依赖属性的原因是什么?

【问题讨论】:

    标签: .net wpf silverlight dependency-properties


    【解决方案1】:

    查看 MSDN 上的 Dependency Properties Overview(或 Silverlight version)。

    (Silverlight/WPF) 提供了一组服务,可用于扩展 CLR 属性的功能。这些服务统称为(Silverlight/WPF)属性系统。由 (Silverlight/WPF) 属性系统支持的属性称为依赖属性。

    有关 Silverlight 和 WPF 属性系统之间的差异,请参阅here。

    依赖属性系统以及将属性与 Silverlight/WPF 类型系统连接起来很重要的几个原因如下(请参阅here 以获得更详细的解释):

    • 允许在样式中设置属性
    • 允许属性支持数据绑定
    • 允许属性是动画的
    • 允许自定义控件的属性在 Visual Studio 中获得设计器支持

    请注意,依赖属性通常由 CLR 属性支持,这意味着您可以在代码中与它进行交互,就像使用 CLR 属性一样。

    【讨论】:

    • 我已经看到了 msdn 链接,它远没有揭开主题的神秘面纱。因此,这个问题。
    【解决方案2】:
    1. 它们在 SL 和 WPF 中是否不同?
      否

    2. 为什么我们真的需要它们?
      简短的回答是,我们需要它们来进行数据绑定和样式设置,这就是 MS 添加它们的原因。

    3. 它们是静态的意味着它们的值是共享的吗?
      静态部分是属性的名称,因此可以使用 GetValue(propName) 检索并使用 SetValue(propName,值)

    【讨论】:

    • '我们需要它们来进行数据绑定和样式设置'。为什么我们不能只使用带有一些通知机制的 clr 属性?我没有得到你的第 3 点。
    • 动画也需要它们。 DP 的目的基本上是让第三方进来并接管您的财产一段时间,然后在他们找到它时归还它,而不会产生很多开销。样式将出现并将值注入属性,动画也是如此。动画尤其需要更改属性的值,然后在动画结束后将它们恢复为原始值。这就是 DP 所启用的。
    • 但是为什么一个普通的 CLR 属性不能解决这个问题呢?我不是说它会,我只是说(就像 OP)我还不明白。
    • @Sandbox 你是正确的,因为你可以为属性创建一个通知系统,以便它们的值在绑定中得到更新。 Microsoft 实际上为此目的创建了一个接口 INotifyPropertyChanged。您也可以绑定到常规属性,但如果没有 INotifyPropertyChange,它将是一次性交易,并且当值更改时绑定不会再次更新。
    【解决方案3】:

    其他人已经提供了依赖属性的解释。我将尝试围绕他们的设计提供一些背景信息。 (它帮助我理解了为什么这些被称为依赖属性的奇怪创造首先存在。)WPF 的设计者添加依赖属性而不是使用普通属性有几个原因......

    1. 能够为不适用于当前元素但适用于其子元素的属性添加值。例如,在容器上设置字体并将其级联到任何包含的文本元素。
    2. 未使用属性的大小成本。以上面的字体示例... 在精心设计的页面上,只有少数元素的字体会被修改——可能是一些顶级容器。如果我们将 Font 存储为常规 C# 属性,则每个对象都需要一个指向字体对象的 32/64 位指针,其中大多数为 null 或具有默认值。通过 XAML 属性的数量和页面上的典型元素数量来放大这一点,您最终需要大量空间来存储任何内容或默认值。将此与依赖属性进行比较,其中值仅在设置时才占用空间。 (我没有查看内部实现,但我认为 GetValue(propName) 和 SetValue(propName) 将值存储在某种每个对象/每个属性的哈希表中。)

    如果微软提供一种更好的语法来创建新的依赖属性,通过使用编译器魔法隐藏重复的代码,就像他们为迭代器所做的那样,那就太好了。也许在 C# 6 中... :)

    【讨论】:

    • +1 希望编译器魔法可以解决丑陋的实现。
    • +1。第 2 项是 DP 的真正杀手锏,因为 DP 是按原样实现的。
    • @James Kovacs:您能否进一步解释您提出的观点 - “将此与依赖属性进行比较,其中值仅在设置时才占用空间。”另外,你能指出一些关于第 2 点的链接吗?
    • @Ken:我永远不会说永远不会,但我不会屏住呼吸。 DependencyObject 位于 System.Windows 命名空间中,使其远离编译器喜欢使用类型的地方。我想不出编译器知道的任何不在 System 命名空间中的类型。
    • @Sandbox - 使用依赖属性,如果您实际设置它,它只会占用值哈希表中的空间。没有包含空值的内存位置。这更有意义吗?至于参考,我没有任何具体的东西。这是从多年前 Don Box 和 Chris Anderson 的一次未录音谈话中收集到的。
    【解决方案4】:

    您提出的大多数问题都已得到其他人的回答,但我会简单介绍一下。

    首先,它们在 Silverlight 和 WPF 之间有所不同,WPF 依赖属性具有更多功能。但是,除了 WPF 中的这些附加功能外,它们基本相同。

    最大的问题是您应该“何时”使用它们(“为什么”大部分已经得到解答)。当您需要在 XAML 中为它们分配标记扩展(例如绑定表达式)时,您基本上需要它们,然后将在运行时解析/评估。大多数情况下,只有在编写公开属性的自定义控件时才需要这样做。

    即您将自定义控件拖放到 XAML 中的表面上,并将绑定表达式分配给其属性之一。

    我看到有些人到处使用它们,认为在进行 Silverlight/WPF 开发时需要它们,但事实并非如此。不同的人在沙地上画出不同的线条,但我说只在必要时使用它们——这几乎 100% 的时间都在自定义控件中。

    明显的自我推销:我在我的书 Pro Business Applications in Silverlight 4 的第 10 章对此主题进行了更深入的(特定于 Silverlight)的讨论。

    【讨论】:

      【解决方案5】:

      WPF 是围绕基于属性的架构设计的,它需要一个非常强大的属性系统支持 -

      1. 其值的各种提供程序,即其值可以在运行时由各种提供程序(如触发器、样式、动画、主题等)更改。(依赖属性值优先级)
      2. XAML(表达式支持)。
      3. 内存效率高(它们的静态特性)。
      4. 更改通知
      5. 属性值继承(附加属性)。

      我发现非常有用的文章 -

      中的依赖属性概述 WPF: http://joshsmithonwpf.wordpress.com/2007/06/22/overview-of-dependency-properties-in-wpf/

      内部依赖属性: http://www.i-programmer.info/programming/wpf-workings/443-inside-dependency-properties-.html

      WPF:依赖的静态本质 特性: http://dotnetslackers.com/Debugger/re-126399_WPF_The_Static_Nature_of_Dependency_Properties.aspx

      【讨论】:

        【解决方案6】:

        添加到其他答案中已经涵盖的内容:

        从概念上讲,依赖属性的核心是一个系统,允许查询基于不同来源的列表获取值,而不是返回单个存储值。正如许多其他答案中列出的那样,这有很多后果,但实际发生的事情会导致在获取值时表现出这样的行为(当然是大大简化了):

        private string _coerced;
        private string _animated;
        private string _local;
        private string _triggered;
        private string _styled;
        private string _inherited;
        private string _default;
        
        public string MyDP
        {
            get
            {
                return _coerced ?? _animated ?? _local ?? _triggered ?? _styled ?? _inherited ?? _default;
            }
        }
        

        【讨论】:

          【解决方案7】:

          答案就在名称本身,尽管“依赖”这个词在软件开发中充满了意义,以至于它的含义并不是特别清楚。

          依赖属性是一个对象的属性,它的值依赖在某个other对象上。所以,例如:

          • TextBox 的FontFamily 属性的值可以(并且通常确实)取决于其容器的FontFamily 属性。如果更改容器上的属性,TextBox 上的值会更改。

          • TextBox 的 Text 属性的值可能取决于绑定的数据源。当绑定属性的值发生变化时,Text 属性的值也会发生变化。

          • Label 的 Opacity 属性的值可能取决于动画情节提要,在常见情况下,您已设置 UI 元素以淡入或淡出以响应某些事件.

          • 任何 UI 元素上各种属性的值都取决于您应用到它们的样式。

          依赖背后的核心概念是,所依赖的事物应该从它所依赖的事物中获取属性值。这就是为什么依赖属性被实现为一个 CLR 属性,它的 getter 调用一个方法。

          当TextBox 或渲染它的东西需要知道它的FontFamily 是什么时,FontFamily getter 调用GetValue 方法。该方法从容器、动画、绑定等中查找值。

          这种方法非常复杂。例如,如果值是继承的,它的工作方式与 WPF 在资源字典中查找样式的方式非常相似:它在本地字典中查找值,如果没有条目,则在其父字典中查找,并且一直如此,直到找到一个值或到达层次结构的顶部,在这种情况下,它使用默认值。

          如果您查看依赖属性的实现,您就会发现。依赖对象有一个字典,它可能包含也可能不包含给定属性的条目。 GetValue 方法从该字典中获取值(这就是具有依赖属性的对象可以具有覆盖它们所继承的本地值的方式),如果它没有找到该值,它使用关于属性的元信息来弄清楚它应该在哪里看。

          由于该类中的每个对象的元信息都是相同的(即,TextBox.Text 对每个 TextBox 的作用相同),因此存储它的字典是该类的静态属性。

          所以当你看到这样的代码时:

          static Button()
          {
             // Register the property
             Button.IsDefaultProperty = 
               DependencyProperty.Register("IsDefault",
               typeof(bool), typeof(Button),
               new FrameworkPropertyMetadata(false,
                  new PropertyChangedCallback(OnIsDefaultChanged)));
          }
          

          正在发生的事情是,定义所有 Button 对象上的 IsDefault 属性的元信息被添加到该字典中。当你看到这个时:

          public bool IsDefault
          {
            get { return (bool)GetValue(Button.IsDefaultProperty); }
            set { SetValue(Button.IsDefaultProperty, value); }
          }
          

          您看到的是基于元信息查找属性值(从本地字典、父对象或其他)的 getter 方法。

          还记得我说过 getter 查找属性值的第一个位置是对象的本地字典吗? setter 中的SetValue 方法是如何将该条目添加到字典中(如果曾经调用过,只有当您通过显式设置属性来覆盖依赖项时才会出现这种情况,即说“我想要这个TextBox在Consolas 中显示文本,而不管窗口中的其他控件正在使用什么。")。

          我们从这种明显复杂的系统中获得的一个非常重要的好处是,对象的依赖属性只有在设置时才会消耗内存。如果我创建 10,000 个TextBox 控件并将它们添加到Window,则其中没有一个实际上包含对FontFamily 对象的引用。那是 10,000 个对象引用,我没有为其分配内存,垃圾收集器也没有检查。事实上,如果 TextBox 有 100 个依赖项属性(它确实有),那么无论何时创建一个 TextBox,这就是 100 个支持字段,您不会为其分配内存。依赖属性仅在显式设置时才会消耗内存。由于 UI 对象上的绝大多数属性永远不会被显式设置,因此可以节省大量资金。

          【讨论】:

            猜你喜欢
            • 2016-02-24
            • 1970-01-01
            • 2011-06-19
            • 1970-01-01
            • 2017-07-29
            • 2012-01-25
            • 1970-01-01
            • 1970-01-01
            • 2011-11-07
            相关资源
            最近更新 更多