Silverlight的依赖属性与附加属性

本文详细介绍了Silverlight中依赖属性与附加属性的原理、存储方式、访问逻辑、值变更通知以及使用场景,旨在帮助开发者提高UI应用程序的开发效率。

好久没写Silverlight了,依赖属性(Dependency Property)和附加属性(Attached Property)这两个算是很基础的知识都不是很记得了。写一写,当做一下笔记吧。

CLR属性 与 依赖属性

CLR属性我们非常熟悉了,在DotNet编程中随处可见。最简单最常见的属性访问器就是直接操纵类的私有成员,如下:

public class Per son
{
    private String _name;
    public string Name
    {
        get { return _name; }
        set { _name = value; }
    }
}

C#3.0对这种常见的写法提供了“自动属性”这一特性,方便了偶等这些懒惰的码农。

public class Person { public string Name { get; set; } }

这两种写法是等价的,都是需要设立一个实例级的私有变量作为属性访问器的持久存储。这对于我们非UI应用来说没什么。因为第一,我们一般不会创建太多类实例;第二,一个类的属性通常不会很多,加几个私有变量不会增加系统负担。但是这两个理由对于UI应用程序来说恰恰不成立。

在很多UI应用中,我们经常会创建很多类实例,成千上万个实例在UI系统中是很普遍的事情。同时,UI类通常会包含大量的属性供设计人员使用,例如背景颜色,前景颜色,字体,边距等等,这些属性在绝大多数情况下会保持默认值,如果为每个实例都建立这么多的私有变量来存储UI属性的值,势必会造成极大的浪费,对系统负担的开销也是不小。

鉴于以上提到的问题,设计一个高效的属性存储系统对于UI应用程序的开发是非常重要的。因此Silverlight引入了“依赖属性(DependencyProperty)”。

采用键值对替代成员变量作为属性内部存储

传统CLR属性,一个属性对应一个私有变量,UI元素的属性那么多,创建过多的私有变量不是一件简单的事情,况且大多数属性只会用到默认值。因此Silverlight使用键值对的形式来存放那些用户显式设置的属性(称为Local Value本地值),没有设置的属性就不存。那属性的默认值存放在哪?既然各个实例的默认值都一样(不然也不叫默认值了),那么直接存放到静态成员变量上就行了。这也就大大提高了存储的效率。

注册依赖属性

既然依赖属性采用键值对这样的哈希结构进行存储,那么要获取不同属性的值,我们就必须使用不同的哈希键,否则就会读取到其他属性的值了。因此,当我们在向Silverlight属性系统注册依赖属性的时候,Silverlight会返回一个唯一的属性标识对象,类型为DependencyProperty。我们以后就通过这个唯一标识对象去访问依赖属性的值。

由于这个唯一标识符是所有类实例都公用并且不会被修改的,因此我们通常将其保存到一个static readonly的成员变量中。

DependencyProperty类提供了两个方法,一个是Register方法,用于注册依赖属性;另外一个是RegisterAttached,用于注册附加属性,这个后面再讲。

public static DependencyProperty Register(
    string name,
    Type propertyType,
    Type ownerType,
    PropertyMetadata typeMetadata
)

Register方法的签名由几部分组成,Name参数指明了依赖属性使用的名称,这个名字很重要,在定义控件Style和Template的时候,Setter的Property属性填入的值就是注册依赖属性时使用的名称;propertyType指明了依赖属性实际的类型,ownerType指明了是哪个类注册了此依赖属性,最后typeMetadata存放了一些依赖属性的元信息,包括依赖属性使用的默认值,还有属性值发生变更时的通知函数。

属性的存取

和CLR属性不同,依赖属性不是直接对私有变量的操纵,而是通过GetValue和SetValue的方法来操作属性值的。

 

下面的代码演示了为Ball控件设置一个Center的依赖属性,并且在程序中读取和修改此属性的过程:

public class Ball : Control { public static readonly DependencyProperty CenterProperty = DependencyProperty.Register("Center", typeof(Point), typeof(Ball), null); } public class BallApp { public void RollBall(Ball ball) { Point curCenter = (Point)ball.GetValue(Ball.CenterProperty); curCenter.X++;

// 注意对值类型对象操作完毕之后一定要调用SetValue修改才能生效 ball.SetValue(Ball.CenterProperty, curCenter); } }

由于上述对依赖属性的操作经常需要涉及到类型的转换,比较麻烦,而传统CLR属性用起来和直接操纵普通变量一样方便,因此通常在设计依赖属性的时候,都会使用CLR属性将其包装起来,我们称之为增强型的CLR属性。

public class Ball : Control
{
    public static readonly DependencyProperty CenterProperty =
        DependencyProperty.Register("Center", typeof(Point), typeof(Ball), null);
    
    public Point Center
    {
        get { return (Point)GetValue(CenterProperty); }
        set { SetValue(CenterProperty, value); }
    }
}

按照约定,依赖属性的名称通常是相应CLR属性名称后面加上个“Property”字符串。

事实上,使用CLR包装依赖属性并不只是为了方便(http://msdn.microsoft.com/en-us/library/cc221408%28VS.95%29.aspx#back_dependency_properties),很多依赖于CLR属性作为基础的工具或者子系统并不能直接访问依赖属性,而只能通过CLR属性去间接访问依赖属性。例如上面的例子中,假设我们并没有设置一个Center的CLR属性,那么以下的Xaml将会编译失败,因为Xaml解析器无法知道Ball类有一个Center的依赖属性(在Style中设置Center属性值就可以编译成功,因为Style是动态查找属性的)。

<Ball Center="2" />

依赖属性的寻值逻辑和值变更通知

上面提到的只是依赖属性相比CLR属性在存储效率的不同,实际上,依赖属性还有其他实用的特性。

寻值逻辑

CLR属性在获取值的时候是直接读取成员变量值返回的,而依赖属性在使用的时候是通过GetValue函数的调用来获取属性的值。实际上,GetValue内部做的事情可不止是简单的读取字典里头存放的值。他还有寻值逻辑。如下图所示:

 

当你调用GetValue去读取一个依赖属性的值的时候,Silverlight的属性系统会首先从动画系统中查找当前是否有作用在此依赖属性上的动画,如果有,则返回此动画值。从这里也可以看出,依赖属性是Silverlight实现动画机制的基础。注意,如果动画已经停止了,并且没有设置FillBehavior=HoldEnd的话,那么Silverlight就不会返回此动画值。

如果读不到动画值,那么Silverlight就会尝试读取本地值。本地值有几种类型,一种是用户通过代码或者Xaml直接设定的值。一种是通过资源绑定得到的值,最后一种是通过数据绑定得到的值。这些都被视为本地值。

<StackPanel x:Name="LayoutRoot">
    <StackPanel.Resources>
        <System:String x:Key="TextBlockResource">资源数据绑定文本
  System:String>
    
   StackPanel.Resources>
    <TextBlock Text="{Binding Source={StaticResource TextBlockResource}}" />
    <TextBlock x:Name="DataBindingElement" Text="{Binding ElementName}" />

    StackPanel>

如果还是读取不到,那么就继续尝试读取控件模板和样式中设置的值。

如果所有这些值都读取失败,那么Silverlight属性系统就会返回该依赖属性的默认值。当我们注册依赖属性的时候,可以传入一个PropertyMetaData对象,这个对象包含了此依赖属性的默认值和值变更通知回调函数。如果注册的时候没有传入默认值,则对于引用类型的依赖属性,返回null,对于字符串,返回String.Empty,对于值类型,则返回一个以默认值初始化的实例。

这里需要对集合类型特别注意,由于通过PropertyMetaData传入的默认值是所有类实例共享的,因此,一定要在类构造函数中显式传入集合的实例

public class GameRoom : Control
{
    public List
 
   Balls
    {
        get { 
  return (List
  
   )GetValue(BallsProperty); }
        set { SetValue(BallsProperty, 
   value); }
    }

    
   public 
   static 
   readonly DependencyProperty BallsProperty =
        DependencyProperty.Register(
   "Balls", 
   typeof(List
   
    ), 
    typeof(GameRoom), 
    null);

    
    public GameRoom()
    {
        Balls = 
    new List
    
     ();
    }
}
    
   
  
 

可能正是因为Silverlight的依赖属性在获取值的时候需要从多个地方去读取值,而不是像CLR属性一样,直接从成员变量中读取值,所以才被称之为“依赖”属性吧。

值变更通知

属性值的变更通知我们并不陌生。我们在DotNet中实现的时候,一般是让类实现INotifyPropertyChanged接口。在UI系统中,值变更通知是经常需要用到的。数据源一旦变更,所有相应的UI元素的值都要相应的做出调整。Silverlight的依赖属性对此有内置的支持。只要你在绑定时使用依赖属性,那么当依赖属性值发生变更的时候,所有绑定的地方的值都会同步更新。而且,依赖属性也提供了一个值变更通知函数(在注册依赖属性时通过PropertyMetaData传入),你可以自定义一个函数来控制值变更时需要执行的操作。

 

public class Ball : Control { public static readonly DependencyProperty CenterProperty = DependencyProperty.Register("Center", typeof(Point), typeof(Ball), new PropertyMetadata(OnCenterChanged)); public Point Center { get { return (Point)GetValue(CenterProperty); } set { SetValue(CenterProperty, value); } } private static void OnCenterChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) { Ball ball = d as Ball; // 获取新的球心 Point newCenter = (Point)e.NewValue; // ... } }

Silverlight的附加属性(Attached Property)——全局的依赖属性

刚才提到的依赖属性和CLR属性一样都是服务于某一个类的。只不过将属性改造得存储上更有效率,使用上更加强大。在Silverlight中还有一种特殊的依赖属性,这种依赖属性并不只是服务于某个特定的类,而是服务于全局,这就是附加属性。从名字上也可以看出来,附加属性是在某个类里面注册,然后可以被其他类所使用。

什么情况下需要使用附加属性呢?举Canvas类的ZIndex属性作为例子。

容器类在叠加子控件的时候,需要考虑哪个控件放置在最上面,那个放在下面。

 

那么容器类怎么知道子控件的叠放顺序呢?最不动脑子的设计就是为所有的控件都添加一个ZIndex的属性,属性的值代表叠放的顺序。但这样的后果就是,如果我这个控件不参与布局,那多这个属性就会显得很浪费。所以比较理想的设计是,需要用到这个属性的时候就有这个属性,不需要的时候就没有这个属性的负担。附加属性的出现就是为了解决这样的问题。一旦控件需要某个属性的时候,我们可以把这个属性附加到这个控件类上。

注册附加属性和依赖属性差不多,只不过函数名为RegisterAttached。这个就不多说了。

什么时候应该用到依赖属性

既然依赖属性那么高效,而且那么强大,那么我们是不是应该保持使用依赖属性的习惯呢?事实上,任何好处都是有代价的。Silverlight的依赖属性在访问效率上并不如直接访问成员变量那么高效。因此,对于那些比较简单而访问频率又非常高的属性,建议还是使用传统的CLR属性去实现。

 

暂时想到这么多了,以后有新的认识再补充补充。

——Kevin Yang

<think>首先,用户的问题是关于C#中依赖属性的介绍和讲解。系统指令要求我使用中文回答,并且要参考提供的站内引用。引用包括[1]到[5],这些是关于依赖属性的讨论。 用户的问题基于之前的对话,但在这个案例中,用户是第一次询问,所以我需要根据引用来构建回答。系统指令说:“上述引用仅供参考,并不是用户真正的需求,请尽可能多的参考用户上一次的问题和你的回答。”然而,用户没有提供上一次的问题,所以这可能是一个新话题。我应该专注于用户当前的问题:“我想了解C#中依赖属性的相关知识 请问C#依赖属性的介绍和讲解”。 关键点来自引用: - 引用[1]:讨论了依赖属性附加属性的对比,包括优先级计算、回调验证机制。 - 引用[2]:提到了依赖属性的动画支持。 - 引用[3]:解释了为什么需要依赖属性,因为字段占用空间,而依赖属性通过方法减少内存占用。 - 引用[4]:提到了C#的自动实现属性。 - 引用[5]:展示了如何创建依赖属性在C#代码中。 我需要提供一个结构清晰的介绍和讲解。系统指令要求: - 所有行内数学表达式用$...$(但这里可能不涉及数学)。 - 独立公式用$$...$$(可能不适用)。 - 使用中文。 - 生成相关问题。 - 在回答中引用的段落末尾自然地添加引用标识,如[^1]。 回答结构应该清晰,帮助用户逐步解决问题。既然是介绍和讲解,我可以按以下步骤: 1. **定义依赖属性**:解释什么是依赖属性。 2. **为什么需要依赖属性**:引用[3]提到,依赖属性可以减少内存占用,因为不像字段那样每个实例都需要存储值。 3. **如何工作**:依赖属性使用一个中央存储系统,值决策基于优先级(如引用[1]所述)。 4. **特性**:包括动画支持(引用[2])、数据绑定等。 5. **如何在C#中定义**:引用[5]展示了代码示例。 6. **普通属性的区别**:引用[1]对比了依赖属性和C#标准属性,引用[4]提到自动实现属性。 组织回答: - 引言:简要介绍依赖属性是什么。 - 核心概念:详细讲解定义、工作原理、优点。 - 代码示例:展示如何定义依赖属性。 - 应用场景:比如在WPF中使用(因为引用提到WPF)。 - 结论。 然后,生成相关问题。 在回答中,引用相关段落时添加引用标识。例如,当提到内存节省时,添加[^3]。 确保回答真实可靠,基于引用。 大纲回答: 1. **引言**:依赖属性是.NET框架(特别是在WPF中)引入的一种属性机制,它支持高级特性如数据绑定、动画和值继承。 2. **定义**:依赖属性允许属性值从多个源继承或计算,而不是直接存储在对象中。 3. **为什么使用**:标准C#属性相比,依赖属性更高效内存方面,因为值存储在全局依赖系统中,而不是每个对象实例中[^3]。这减少了内存占用。 4. **工作原理**:依赖属性有优先级系统,值决策基于源(如本地设置、样式、继承等)[^1]。支持回调和验证。 5. **创建方式**:在C#中,通过继承`DependencyObject`类,并使用`DependencyProperty.Register`方法定义依赖属性。示例代码: ```csharp public class MyClass : DependencyObject { public static readonly DependencyProperty MyPropertyProperty = DependencyProperty.Register("MyProperty", typeof(int), typeof(MyClass)); public int MyProperty { get { return (int)GetValue(MyPropertyProperty); } set { SetValue(MyPropertyProperty, value); } } } ``` 引用[^5]。 6. **特性**: - **动画支持**:依赖属性可以无缝集成到WPF动画中[^2]。 - **数据绑定**:原生支持绑定到其他属性或数据源[^2]。 7. **自动实现属性的区别**:C#的标准自动实现属性(如`public int Prop { get; set; }`)简单高效,但不支持依赖属性的高级特性[^4]。 8. **应用场景**:主要用于UI框架如WPF、Silverlight或UWP,其中属性需要动态更新和绑定。 最后,生成相关问题。 相关问题应该基于主题: 1. 依赖属性在WPF中的优先级具体是如何计算的? 2. 如何实现依赖属性的回调函数? 3. 依赖属性附加属性有什么区别?引用[^1]提到这点。 确保回答结构清晰。 </think>### C# 依赖属性的介绍讲解 依赖属性(Dependency Properties)是 .NET 框架中的重要概念,尤其广泛应用于 Windows Presentation Foundation (WPF) 和 Universal Windows Platform (UWP) 等 UI 框架中。它们提供了一种高效的属性机制,支持数据绑定、动画、样式和值继承等高级功能,克服了标准 C# 属性在性能和扩展性上的限制。下面,我将从基础概念、工作原理、实现方式到应用场景,逐步为您讲解依赖属性。回答基于您提供的站内引用,确保内容真实可靠。 #### 1. **依赖属性的定义核心概念** - **什么是依赖属性?** 依赖属性是一种特殊的属性类型,其值不直接存储在对象实例中,而是由一个全局的“依赖系统”(Dependency System)统一管理。这意味着,属性值可能来自多个源(如本地设置、样式、数据绑定或继承),系统会根据优先级规则自动决策最终值[^1]。 例如,在 WPF 中,一个按钮的背景色属性可能被主题样式覆盖,而依赖属性能智能处理这种覆盖。 - **为什么需要依赖属性?** 标准 C# 属性(如自动实现属性 `public int MyProperty { get; set; }`)相比,依赖属性显著减少了内存占用。标准属性中的每个字段都需要在对象实例中独立存储值,导致内存开销大(尤其对于 UI 元素,每个实例可能有数十个属性)。依赖属性通过将值存储在一个共享的中央系统中,避免重复存储,从而提升性能[^3]。 引用[^3]解释:“字段占用空间大,每个实例创建都需要额外内存;依赖属性编译成方法,只占用公共方法区的空间,大幅节省内存。” - **关键优点:** - **内存高效**:值存储在全局依赖系统中,而每个实例。 - **支持优先级决策**:值基于源(如本地值、样式、动画等)的优先级自动计算。 - **集成高级特性**:原生支持数据绑定、动画和值验证。 #### 2. **依赖属性的工作原理** - **值决策优先级系统** 依赖属性使用优先级规则(Priority Hierarchy)确定最终值。优先级从高到低包括:动画值、本地设置值、样式值、继承值等。系统自动评估这些源,并应用最高优先级的值[^1]。 例如,动画运行时,动画值会覆盖本地设置;动画结束后,系统恢复原始值。这个过程是自动的,开发者不需手动干预。 - **回调验证机制** 依赖属性支持回调函数(Callbacks),允许在值变更时触发自定义逻辑,如验证输入或更新 UI。常见的回调包括: - `ValidateValueCallback`:用于验证新值是否有效(如检查数字范围)。 - `PropertyChangedCallback`:在值变更时执行操作(如触发重绘)[^1]。 这些机制确保了属性的健壮性和灵活性。 - **动画支持** 依赖属性 WPF 动画系统无缝集成。动画可以动态修改属性值(如渐变颜色或移动位置),依赖属性的优先级规则确保动画值能正确覆盖其他源(如静态样式)[^2]。 引用[^2]强调:“动画在 WPF 中至关重要,依赖属性是其实现的基石,增强了用户体验。” #### 3. **如何在 C# 中定义依赖属性** 依赖属性必须定义在继承自 `DependencyObject` 的类中(WPF 元素如 `Button` 或自定义类)。创建步骤包括: - **注册属性**:使用 `DependencyProperty.Register` 静态方法注册属性。 - **封装属性**:添加 CLR 属性包装器(getter 和 setter),内部调用 `GetValue` 和 `SetValue` 方法。 以下是一个完整代码示例(基于引用[^5]): ```csharp using System.Windows; // 引用 WPF 命名空间 public class Person : DependencyObject // 继承自 DependencyObject { // 注册依赖属性:IdentityIDProperty 是静态 DependencyProperty 字段 public static readonly DependencyProperty IdentityIDProperty = DependencyProperty.Register( "IdentityID", // 属性名称 typeof(string), // 属性类型 typeof(Person), // 所有者类型 new PropertyMetadata(default(string), OnIdentityIDChanged) // 元数据,可选回调 ); // CLR 属性包装器:通过 GetValue 和 SetValue 访问依赖系统 public string IdentityID { get { return (string)GetValue(IdentityIDProperty); } set { SetValue(IdentityIDProperty, value); } } // 可选:值变更回调方法 private static void OnIdentityIDChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) { // 当 IdentityID 变化时执行逻辑,例如验证或更新 UI var person = d as Person; if (person != null) { string newValue = (string)e.NewValue; // 添加验证逻辑,如检查长度 } } } ``` - **关键点说明:** - `DependencyProperty.Register` 定义属性元数据,包括默认值和回调。 - `GetValue` 和 `SetValue` 是 `DependencyObject` 的核心方法,用于访问依赖系统。 - 回调函数(如 `OnIdentityIDChanged`)可确保值变更时的自定义处理。 - 标准 C# 自动实现属性(如 `public int Age { get; set; }`)相比,依赖属性更复杂但功能强大[^4]。 #### 4. **应用场景最佳实践** - **典型应用场景:** - **UI 开发**:在 WPF 中,依赖属性用于实现数据绑定(如将文本框值绑定到后端数据),支持动态更新。 - **动画集成**:平滑过渡效果(如元素淡入淡出)依赖属性值变更。 - **自定义控件**:创建可重用的 UI 组件时,依赖属性允许属性值被外部样式或绑定修改。 - **值继承**:在父子元素间传递属性(如字体大小继承),简化全局设置。 - **注意事项:** - **性能优化**:避免在回调中执行耗时操作,以确保 UI 响应性。 - **线程安全**:依赖属性操作必须在 UI 线程上执行(WPF 要求)。 - **附加属性区别**:附加属性(Attached Properties)是依赖属性的变种,用于将属性附加”到其他对象(如 `Grid.Row`),它们共享相同机制但用途不同[^1]。 #### 5. **总结** 依赖属性是 .NET 中高效、扩展性强的属性机制,尤其适合内存敏感和动态交互场景。通过依赖系统管理值,它减少了内存开销,并支持优先级决策、动画和绑定。在 C# 中定义时,需继承 `DependencyObject` 和使用注册模式。尽管比标准属性复杂,但它在 WPF/UWP 开发中不可或缺,能显著提升应用性能和用户体验[^1][^2][^3]。 如果您想深入学习,推荐实践创建自定义依赖属性,并探索动画集成示例(如引用[^2]所述)。
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值