使用委托,事件,接口解耦(View和ViewModel)

MVVM 观察者模式

参考知乎-https://zhuanlan.zhihu.com/p/676107986

1.委托

  • 委托是一个函数指针,表示对某个方法的引用。

.NET内置委托类型

  • Action(无返回值)
  • Func(有返回值)
  • 返回一个布尔值Predicate<int> isEven = (number) => number % 2 == 0; // 接受一个整数参数并判断是否为偶数

举例:

1
Action<string> del = message => Console.WriteLine("你说:" + message);`
  • Action<string>等价于delegate void MyDelegate(string msg)

  • message => ...Lambda 表达式,等价于一个匿名方法。

1
2
3
4
    void AnonymousMethod(string message)
{
Console.WriteLine("你说:" + message);
}

使用Invoke调用委托

1
2
3
4
5
6
Action<string> showMessage = msg => Console.WriteLine(msg);

// 这两种写法是等效的
showMessage("你好");
showMessage.Invoke("你好"); // 调用委托,效果一样

  • Invoke可能会配合空值条件运算符一起使用,例如:myAction?.Invoke("Hello");,只有当myAction被赋值了,即委托变量指向某个方法了”,才会调用委托。

MVVM架构中使用委托,事件,接口解耦

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// ViewModel层
public class MyViewModel
{
public Action<string> ShowMessageAction { get; set; }

public void DoSomething()
{
// 调用 UI 层传进来的委托,而不是直接调用 MessageBox
ShowMessageAction?.Invoke("处理完成");
}
}
// View层
var vm = new MyViewModel();
vm.ShowMessageAction = msg => MessageBox.Show(msg);
this.DataContext = vm;

2.事件

  • 事件是对委托的封装,是用来做发布-订阅的一种通信机制。事件声明在一个类中,它需要委托类型的名称,任何注册到事件的处理程序都必须与委托类型的签名和返回类型匹配。

upload successful

事件举例

  1. 发布者(Publisher)
    定义:负责“声明”并“触发”事件的类或结构。
    职责:
    • 用 event 关键字声明事件。
    • 在合适的业务时机“触发”事件。

示例:

1
2
3
4
5
6
7
8
9
10
11
public class StockPrice  // 发布者
{
public event Action<string, decimal> OnPriceChanged; // 声明事件
private decimal _price;

public void UpdatePrice(decimal newPrice)
{
_price = newPrice;
OnPriceChanged?.Invoke("AAPL", _price); // 触发事件
}
}
  1. 订阅者(Subscriber)
    定义:对某事件感兴趣,并向发布者“注册”自己的类或结构。
    职责:
    • 拿到发布者实例的引用。
    • 使用 += 把符合签名的方法挂到事件上。
    • 必要时用 -= 取消订阅,防止内存泄漏。

示例:

1
2
3
4
5
6
7
8
9
10
11
12
public class Investor  // 订阅者
{
public void Subscribe(StockPrice stock)
{
stock.OnPriceChanged += HandlePriceChanged; // 注册
}

private void HandlePriceChanged(string symbol, decimal price)
{
Console.WriteLine($"{symbol} 最新价:{price}");
}
}
  1. 事件处理程序(Event Handler)
    定义:订阅者提供的、与事件签名匹配的方法。
    特点:
    • 参数由事件声明决定(常见为 void MethodName(object? sender, EventArgs e);)。
    • 在事件被触发时,由发布者“顺序调用”。
    示例:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
public class MyEventArgs : EventArgs
{
public string Message { get; set; }
}

public class Button // 发布者
{
public event EventHandler<MyEventArgs> Clicked;
public void Click()
{
Clicked?.Invoke(this, new MyEventArgs { Message = "按钮被按下" });

// this 把 Button 自己当作 sender 传出去;
//new MyEventArgs { … } 把附带数据发出去;
}
}


public class Form1 // 订阅者
{
public Form1(Button btn)
{
btn.Clicked += Button_Clicked; // 事件处理程序
}

private void Button_Clicked(object? sender, MyEventArgs e)
{
MessageBox.Show(e.Message);
}
}
  1. 触发(Raise / Fire / Invoke)事件
    定义:发布者在内部满足某条件时,执行事件委托列表中的所有方法。
    关键点:
    • 用 null-条件运算符 ?. 判空,避免无订阅者时抛 NullReferenceException。
    • 调用顺序 = 订阅顺序(多播委托)。
    • 若某处理程序抛异常,后续处理程序仍可用 try/catch 逐个保护。

3. 使用接口注入 UI 行为

定义一个接口,例如:

1
2
3
4
public interface IMessageService
{
void ShowMessage(string message);
}

ViewModel 依赖这个接口,而不关心它的实现:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
public class MyViewModel
{
private readonly IMessageService _messageService;

public MyViewModel(IMessageService messageService)
{
_messageService = messageService;
}

public void DoWork()
{
_messageService.ShowMessage("工作完成");
}
}

在 View 层实现接口并传入:

1
2
3
4
5
6
7
8
9
10
11
public class MessageService : IMessageService
{
public void ShowMessage(string message)
{
MessageBox.Show(message);
}
}

// 创建 VM 时注入
var vm = new MyViewModel(new MessageService());
this.DataContext = vm;

使用委托,事件,接口解耦(View和ViewModel)
https://weihehe.top/2025/08/05/委托,事件,响应/
作者
weihehe
发布于
2025年8月5日
许可协议