WPF-ViewModel属性机制
目录
官方定义
属性是一种成员,它提供灵活的机制来读取、写入或计算私有字段的值。属性可作为公共数据成员使用,但它们是称为"访问器"(accessors)的特殊方法。这使得可以轻松访问数据,还有助于提供方法的安全性和灵活性。
官方把属性归类为方法,不是字段。
一、C#属性(Property)
在C#中,属性用于 对字段进行封装访问。
基本结构:
private string _name;
public string Name
{
get { return _name;}
set { _name = value;}
}结构说明:
| 部分 | 作用 |
|---|---|
| _name | 内部字段(Field) |
| Name | 对外公开属性(Property) |
| get | 读取属性值 |
| set | 设置属性值 |
访问示例:
Name = "Alice"; // 调用set
var n = Name; // 调用 get二、value的本质
1.value是什么?
value 是 C# 在属性 set 访问器中自动提供的隐式参数。
它表示:
外部赋给该属性的新值示例:
set
{
_name = value;
}当执行:
Name = "Alice";实际发生:
调用 set("Alice")因此:
value = "Alice"2.value的作用域
value 只存在于 set 代码块中。
合法:
set
{
Console.WriteLine(value);
}非法:
get
{
Console.WriteLine(value);
}原因:
value 是 set 方法参数三、属性的底层原理
属性在编译后会变成 两个方法:
public string Name
{
get { return _name;}
set { _name = value; }
}概念上等价于:
private string _name;
public string GetName()
{
return _name;
}
public void SetName(string value)
{
_name = value;
}调用:
Name = "Alice";等价于:
SetName("Alice");四、为什么需要字段(Field)
public string Name
{
get { return Name;}
set { Name = value;}
}问题:无限递归调用
执行流程:
Name = "Alice"
↓
set
↓
Name = value
↓
再次调用 set
↓
无限循环
访问 Name 时:
obj.Name
→ 调用 get 访问器
→ get 中 return Name
→ 再次调用 get 访问器
→ 再次 return Name
→ 再次调用 get 访问器
→ ... 无限循环 → StackOverflowException
设置 Name 时:
obj.Name = "张三"
→ 调用 set 访问器
→ set 中 Name = value
→ 再次调用 set 访问器
→ 再次 Name = value
→ 再次调用 set 访问器
→ ... 无限循环 → StackOverflowException最终会引发异常:StackOverflowException
正确写法:
private string _name;
public string Name
{
get { return _name; }
set { _name = value; }
}结构:
Property
↓
Field而不是:
Property → Property五、自动属性(Auto Property)
如果不需要额外逻辑,可以使用自动属性:
public string Name { get; set; }编译器会自动生成隐藏字段:
<Name>K_BackingField但在WPF ViewModel 中不常用直接使用,原因是:
需要 PropertyChanged 通知 UI 更新六、WPF数据绑定原理
在 WPF,UI 和 ViewModel 通过 Binding 连接。
<TextBox Text="{Binding Name}"/>执行流程:
UI输入
↓
Binding
↓
ViewModel.Name = 新值
↓
set(value)
↓
更新字段
↓
通知 UI如果没有通知:UI不会刷新
七、INotifyPropertyChanged
WPF 使用接口: INotifyPropertyChanged – 用于通知 UI 数据变化。
接口定义:
public interface INOtifyPropertyChanged
{
event PropertyChangedEventHandler PropertyChanged;
}当前属性变化时从触发:
PropertyChanged 事件八、ViewModel 标准写法
public class MainViewModel : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
private string _name;
public string Name
{
get => _name;
set =>
{
if(_name != value)
{
_name = value;
OnPropertyChanged(nameof(Name));
}
}
}
protected void OnPropertyChanged(string propertyName)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}流程:
set(value)
↓
字段更新
↓
PropertyChanged
↓
Binding监听
↓
UI刷新九、为什么要写 If (_field != value)
原因:
避免 重复刷新 UI。
如果没有判断:
set
{
_name = value;
OnPropertyChanged(nameof(Name));
}即使值没有变化:
仍然触发 UI 更新判断后:
if (_name != value)只在值改变时通知。
优点:
- 减少 UI 刷新
- 提高性能
- 避免循环绑定
十、CallerMemberName(现代写法)
为了避免写字符串"Name", 可以使用:
CallerMemberName改写:
protected void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}调用:
OnpropertyChanged();编译器会自动填充:Name
优点:
- 避免硬编码字符串
- 避免重构错误
十一、MVVM写法(SetProperty)
实际项目中通常封装一个通用方法:
protected bool SetProperty<T>(ref T field, T value,
[CallerMemberName] string propertyName = null)
{
if (EqualityComparer<T>.Default.Equals(field, value))
{
return false;
}
field = value;
OnPropertyChanged(propertyName);
return true;
}使用:
public class MainViewModel : ViewModelBase
{
private string _name;
public string Name
{
get => _name;
set => SetProperty(ref _name, value);
}
}十二、完整ViewModel模板
public class ViewModelBase : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
protected void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
protected bool SetProperty<T>(ref T field, T value,
[CallerMemberName] string propertyName = null)
{
if (EqualityComparer<T>.Default.Equals(field, value))
{
return false;
}
field = value;
OnPropertyChanged(propertyName);
return true;
}
}使用:
public class MainViewModel : ViewModelBase
{
private string _name;
public string Name
{
get => _name;
set => SetProperty(ref _name, value);
}
}执行流程总结:
UI输入
↓
Binding
↓
ViewModel.Property = value
↓
set(value)
↓
SetProperty()
↓
字段更新
↓
PropertyChanged
↓
WPF Binding监听
↓
UI刷新