CLR属性的主要特征
-
封装性:
-
隐藏字段的实现细节
-
提供对字段的受控访问
-
-
访问控制:
-
可单独设置get/set访问器的可见性
-
可创建只读或只写属性
-
-
计算属性:
-
可以在getter中执行计算逻辑
-
不需要直接对应一个字段
-
-
验证逻辑:
-
可以在setter中添加值验证
-
可以抛出异常拒绝无效值
-
-
通知机制:
- 可以手动实现属性变更通知(如INotifyPropertyChanged)
-
线程安全:
- 可以添加线程同步逻辑
CLR属性的实现原理
基本实现
CLR属性本质上是编译器生成的"语法糖",编译后会转换为方法调用:
cs
// 源代码
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引入的自动属性进一步简化了语法:
cs
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属性的高级用法
- 索引器:
cs
public string this[int index] { get { /*...*/ } set { /*...*/ } }
- 表达式体属性(C# 6+):
cs
public string FullName => $"{FirstName} {LastName}";
- 初始化器(C# 6+):
cs
public string Name { get; set; } = "Anonymous";
- 只读自动属性(C# 6+):
cs
public string Id { get; } = Guid.NewGuid().ToString();
CLR属性是C#面向对象编程的基础设施,提供了字段访问的抽象层,既能保持简洁的语法,又能提供灵活的控制逻辑。
- 依赖属性与附加属性
依赖属性(Dependency Property)
实现原理
依赖属性是WPF/Silverlight/UWP等XAML技术中的核心概念,它扩展了传统的CLR属性,提供了更丰富的功能:
-
属性值继承:子元素可以继承父元素的属性值
-
数据绑定支持:可以直接作为数据绑定的目标
-
动画支持:可以被动画系统直接操作
-
样式支持:可以通过样式设置
-
元数据支持:可以指定默认值、验证回调等
-
值优先级系统:多个值源按照优先级决定最终值
实现依赖属性的关键是通过DependencyProperty
类和DependencyObject
基类:
cs
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)
实现原理
附加属性是一种特殊的依赖属性,它允许一个类为其他类定义属性,常用于布局系统和服务模式:
cs
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应用程序至关重要。