C#中的CLR属性、依赖属性与附加属性
-
CLR属性的主要特征
-
封装性:
-
隐藏字段的实现细节
-
提供对字段的受控访问
-
-
访问控制:
-
可单独设置get/set访问器的可见性
-
可创建只读或只写属性
-
-
计算属性:
-
可以在getter中执行计算逻辑
-
不需要直接对应一个字段
-
-
验证逻辑:
-
可以在setter中添加值验证
-
可以抛出异常拒绝无效值
-
-
通知机制:
-
可以手动实现属性变更通知(如INotifyPropertyChanged)
-
-
线程安全:
-
可以添加线程同步逻辑
-
CLR属性的实现原理
基本实现
CLR属性本质上是编译器生成的"语法糖",编译后会转换为方法调用:
// 源代码
public class Person
{private string _name;public string Name{get { return _name; }set { _name = value; }}
}// 编译后相当于
public class Person
{private string _name;public string get_Name(){return this._name;}public void set_Name(string value){this._name = value;}
}
自动实现属性
C# 3.0引入的自动属性进一步简化了语法:
public string Name { get; set; }
编译器会自动生成一个隐藏的私有字段(通常以<Name>k__BackingField命名)和对应的get/set方法。
属性元数据
在IL(中间语言)层面,属性是通过以下元数据表示的:
-
Property表:记录属性名称、类型和访问器方法
-
Method表:存储get/set方法实现
-
Field表:对于自动属性,存储编译器生成的私有字段
属性访问性能
属性访问的性能与方法调用相当,因为:
-
简单属性(get;set;)通常会被JIT内联优化
-
复杂属性(包含逻辑的)与方法调用开销相同
-
虚属性(virtual)会有额外的虚方法调用开销
与依赖属性的比较
特性 | CLR属性 | 依赖属性 |
---|---|---|
存储 | 直接存储在对象中 | 存储在DependencyObject的全局字典中 |
绑定支持 | 需实现INotifyPropertyChanged | 原生支持 |
动画支持 | 不支持 | 原生支持 |
默认值 | 需在构造函数设置 | 可通过元数据指定 |
继承 | 不支持 | 支持属性值继承 |
内存占用 | 每个实例都有存储 | 只有修改过的值才占用内存 |
适用场景 | 普通业务对象 | WPF/Silverlight/UWP控件 |
CLR属性的高级用法
-
索引器:
public string this[int index] { get { /*...*/ } set { /*...*/ } }
-
表达式体属性(C# 6+):
public string FullName => $"{FirstName} {LastName}";
-
初始化器(C# 6+):
public string Name { get; set; } = "Anonymous";
-
只读自动属性(C# 6+):
public string Id { get; } = Guid.NewGuid().ToString();
CLR属性是C#面向对象编程的基础设施,提供了字段访问的抽象层,既能保持简洁的语法,又能提供灵活的控制逻辑。
- 依赖属性与附加属性
依赖属性(Dependency Property)
实现原理
依赖属性是WPF/Silverlight/UWP等XAML技术中的核心概念,它扩展了传统的CLR属性,提供了更丰富的功能:
-
属性值继承:子元素可以继承父元素的属性值
-
数据绑定支持:可以直接作为数据绑定的目标
-
动画支持:可以被动画系统直接操作
-
样式支持:可以通过样式设置
-
元数据支持:可以指定默认值、验证回调等
-
值优先级系统:多个值源按照优先级决定最终值
实现依赖属性的关键是通过DependencyProperty
类和DependencyObject
基类:
public class MyControl : DependencyObject
{// 注册依赖属性public static readonly DependencyProperty MyPropertyProperty = DependencyProperty.Register("MyProperty", // 属性名称typeof(string), // 属性类型typeof(MyControl), // 拥有者类型new PropertyMetadata("默认值")); // 元数据// CLR包装器public string MyProperty{get { return (string)GetValue(MyPropertyProperty); }set { SetValue(MyPropertyProperty, value); }}
}
应用场景
-
自定义控件开发:为自定义控件添加可绑定、可样式化的属性
-
数据绑定:作为数据绑定的目标属性
-
动画:创建可动画化的属性
-
模板绑定:在控件模板中使用TemplateBinding
-
样式设置:通过样式设置多个控件的属性值
附加属性(Attached Property)
实现原理
附加属性是一种特殊的依赖属性,它允许一个类为其他类定义属性,常用于布局系统和服务模式:
public class GridHelper
{// 注册附加属性public static readonly DependencyProperty RowCountProperty =DependencyProperty.RegisterAttached("RowCount", // 属性名称typeof(int), // 属性类型typeof(GridHelper), // 拥有者类型new PropertyMetadata(1, OnRowCountChanged)); // 元数据// Get访问器(必须为public static)public static int GetRowCount(DependencyObject obj){return (int)obj.GetValue(RowCountProperty);}// Set访问器(必须为public static)public static void SetRowCount(DependencyObject obj, int value){obj.SetValue(RowCountProperty, value);}// 属性变更回调private static void OnRowCountChanged(DependencyObject d, DependencyPropertyChangedEventArgs e){if (d is Grid grid){// 当RowCount变化时,调整Grid的行定义grid.RowDefinitions.Clear();for (int i = 0; i < (int)e.NewValue; i++){grid.RowDefinitions.Add(new RowDefinition());}}}
}
应用场景
-
布局系统:如Grid.Row、Grid.Column等
-
服务模式:如ToolTipService.ToolTip、ScrollViewer.IsScrollable等
-
行为扩展:为现有控件添加额外功能
-
自定义布局面板:创建自己的布局容器时定义布局属性
两者比较
特性 | 依赖属性 | 附加属性 |
---|---|---|
定义方式 | 在定义类中使用 | 在任何类中定义,可附加到其他对象 |
注册方法 | Register | RegisterAttached |
访问器 | 实例属性 | 静态方法 |
典型用途 | 为类定义标准属性 | 为其他类扩展属性 |
高级主题
-
属性值优先级:本地值 > 动画 > 本地样式 > 触发器 > 隐式样式 > 样式触发器 > 模板触发器 > 样式Setter > 默认值
-
属性变更回调:通过PropertyMetadata指定属性变化时的处理逻辑
-
验证回调:通过ValidateValueCallback进行值验证
-
强制回调:通过CoerceValueCallback强制属性值在特定范围内
依赖属性和附加属性是WPF等XAML技术的核心机制,理解它们的原理和用法对于开发复杂的XAML应用程序至关重要。