7.1 依赖注入

问题1

依赖注入

AddAppServices = 告诉框架:"我要用这些类,你帮我创建好,我用的时候找你要"。

// 注册 ViewModel(自动注入 ILogService)

services.AddSingleton();

好问题!这行就是告诉容器:"有人要 AxisEcatVM 的时候,你负责创建它,顺便把 ILogService 塞进去"。

三行代码的关系

csharp复制 复制代码
// 1. 注册日志服务
services.AddSingleton<ILogService, FileLogService>();

// 2. 注册 ViewModel
services.AddSingleton<AxisEcatVM>();

// 3. 获取 ViewModel
var vm = _host.Services.GetRequiredService<AxisEcatVM>();

解释:第 2 步到底做了什么?

容器内部做的事:

code复制 复制代码
用户要求:var vm = GetRequiredService<AxisEcatVM>();
    │
    ▼
容器检查:AxisEcatVM 的构造函数需要什么?
    │
    ├─ 发现:public AxisEcatVM(ILogService logService)
    │
    ▼
容器去找:ILogService 注册了吗?
    │
    ├─ 注册了:FileLogService
    │
    ▼
容器创建:new FileLogService()
    │
    ▼
容器注入:new AxisEcatVM(FileLogService实例)
    │
    ▼
返回 vm 给用户

为什么要注册 AxisEcatVM?

不注册的话:

csharp复制 复制代码
services.AddSingleton<ILogService, FileLogService>();
// 没有注册 AxisEVM

var vm = _host.Services.GetRequiredService<AxisEcatVM>();  // ← 报错!
// 容器不知道 AxisEVM 怎么创建

报错:

code复制 复制代码
No service for type 'WpfApp6.ViewModel.AxisEcatVM' has been registered.

一句话总结

code复制 复制代码
AddSingleton<ILogService, FileLogService>()  = 告诉容器:"ILogService 用 FileLogService 实现"
AddSingleton<AxisEcatVM>()                  = 告诉容器:"AxisEVM 我要用,帮我创建"

第二句的效果: 容器发现 AxisEcatVM 构造需要 ILogService → 自动去找已注册的 FileLogService → 塞进去 → 创建 AxisEcatVM 返回。

对比你原来的写法

语法2

csharp 复制代码
// 1. 捕获UI线程异常
this.DispatcherUnhandledException += App_DispatcherUnhandledException;

// 2. 捕获非UI线程异常
AppDomain.CurrentDomain.UnhandledException += CurrentDomain_UnhandledException;

// 3. 捕获异步任务未观察异常
TaskScheduler.UnobservedTaskException += TaskScheduler_UnobservedTaskException!;

好,这三行是**"全局异常兜底"**------专门捕获"代码里没 catch 的漏网之鱼"。

一句话

正常代码里有 try-catch,但万一漏了,程序就崩了。这三行专门补那1%的漏洞。

三个异常处理器分别管什么

1️⃣ DispatcherUnhandledException → UI线程异常

管: Button点击、TextBox输入等 UI 操作的代码抛出的异常

例子(你的项目):

csharp复制 复制代码
// MainWindow.xaml.cs
private void StartScan_Click(object sender, RoutedEventArgs e)
{
    // 如果这里抛异常,且没人 catch
    throw new InvalidOperationException("轴未回零");  // ← 没人 try-catch
}

没加处理器: 程序直接崩溃,窗口消失

加了处理器:

code复制 复制代码
程序不崩 → 弹框提示"轴未回零" → 用户可以关掉继续操作

2️⃣ UnhandledException → 非UI线程异常

管: 后台线程(Task、Thread)抛出的异常

例子(你的项目):

csharp复制 复制代码
// AxisEcatVM.cs
public async Task RunScanTask(...)
{
    await Task.Run(() =>  // ← 后台线程
    {
        // 如果这里抛异常
        throw new Exception("EtherCAT 通讯超时");
    });
}

问题: 后台线程的异常不会弹框通知用户,程序可能静默崩溃

加了处理器:

code复制 复制代码
记录日志 → 程序不崩 → 用户知道出了问题

3️⃣ UnobservedTaskException → async Task 的漏网异常

管: async Task 方法里抛出的、但调用者没 .Wait() 或 await 的异常

例子:

csharp复制 复制代码
// 调用者写法有bug
var task = RunScanTask(...);  // ← 只拿了 Task,没 await
// do something else...

// RunScanTask 里抛异常了,没人 catch
// 这个异常会跑到 UnobservedTaskException
三行代码对应三个"漏洞"
code复制 复制代码
程序正常流程:
try
{
    // 业务代码
}
catch (Exception ex)
{
    // 处理异常
}

但总有些地方漏写了 try-catch:

你的项目里的具体例子

场景:轴移动超时没人catch

csharp复制 复制代码
// AxisEcatVM.cs
public async Task RunScanTask(...)
{
    // 这里有 try-catch,但万一漏了...
    await MoveToAsync(targetX, token);
}

如果 MoveToAsync 里有个 bug 没人发现:

code复制 复制代码
没加处理器 → 线程崩 → 程序崩溃消失
加了处理器 → 记录日志 → 弹框提示 → 程序继续跑

代码里的处理逻辑

csharp复制 复制代码
// 三个处理器最终都调用同一个方法
private void HandleException(Exception ex, string source)
{
    // 1. 记录日志
    AppLogger!.LogError($"操作失败:{ex.Message}");

    // 2. UI线程异常 → 弹框提示用户
    if (source == "UI线程")
    {
        MessageBox.Show($"操作失败:{ex.Message}", "错误", 
            MessageBoxButton.OK, MessageBoxImage.Error);
    }
}

一句话总结

code复制 复制代码
try-catch        = 自己家的安全网(每个方法里自己写)
异常处理器       = 整栋楼的安全网(万一哪家安全网破了,不让整栋楼塌)

三行分工:

UI线程漏了 → DispatcherUnhandledException 补

后台线程漏了 → UnhandledException 补

async Task漏了 → UnobservedTaskException 补

语法3

有啥用:

csharp 复制代码
_host = Host.CreateDefaultBuilder()
.ConfigureServices(AddAppServices)
.ConfigureLogging(AddAppLogging)
.RepairConfigs()
.Build();
ServiceProvider = _host.Services;

好,用大白话解释这五行代码:

一句话

这五行 = 搭建一个"零件超市",Build 之后想买什么直接来拿。

逐行解释

第 1 行:创建超市

csharp复制 复制代码
_host = Host.CreateDefaultBuilder()

比喻: 建了一个空仓库,还没有货架。

第 2 行:摆货架

csharp复制 复制代码
.ConfigureServices(AddAppServices)

比喻: 往仓库里摆货架,把 Home_VM、AxisEcatVM、RepairDbContext 等零件摆上去。

第 3 行:接通监控(日志)

csharp复制 复制代码
.ConfigureLogging(AddAppLogging)

比喻: 给仓库装上监控摄像头,所有日志都录下来(存到 logs/UI/、logs/DB/)。

第 4 行:加载配置文件

csharp复制 复制代码
.RepairConfigs()

比喻: 读取配置文件(如数据库地址、IP端口等)。

第 5 行:开门营业

csharp复制 复制代码
.Build()

比喻: 仓库建设完成,正式开门。

第 6 行:把钥匙交出来

csharp复制 复制代码
ServiceProvider = _host.Services

比喻: 把超市的钥匙存到 ServiceProvider,以后用这个钥匙来取零件。

这六行在你的项目里的作用

不用 DI(你现在的写法):

csharp复制 复制代码
// 启动时:自己 new
MainVM = new MainShellVM();
AxisVM = new AxisEcatVM();
ImageVM = new XrayImageVM();

// 问题:如果 Home_VM 以后需要 LogService
//       要在 MainWindow 里改 new 的代码

用 DI(加上这六行之后):

csharp复制 复制代码
// App.xaml.cs 启动时
_host = Host.CreateDefaultBuilder()
            .ConfigureServices(AddAppServices)  // ← 注册所有零件
            .Build();

// 运行时,想用什么直接从超市拿
var vm = ServiceProvider.GetService<MainShellVM>();

实际例子:在 Home_VM 里用日志服务

你现在的情况:

csharp复制 复制代码
// Home_VM.cs ------ 没法用统一的日志服务
public class Home_VM
{
    public void LoadData()
    {
        Console.WriteLine("加载数据...");  // 只能这样
    }
}

加上 DI 之后:

csharp复制 复制代码
// App.xaml.cs 里注册
services.AddSingleton<ILogService, FileLogService>();

// Home_VM 里直接用
public class Home_VM
{
    public Home_VM(ILogService logService)
    {
        _logService = logService;
    }

    public void LoadData()
    {
        _logService.Log("加载数据...");  // ← 统一的日志服务
    }
}

好处:以后想改日志格式、加日志级别、加 MySQL 日志,只改 App.xaml.cs 一行。

一张图总结

code复制 复制代码
Host.CreateDefaultBuilder()    ← 搭架子
        ↓
.ConfigureServices(注册零件)    ← 摆货架
        ↓
.ConfigureLogging(配置日志)     ← 装监控
        ↓
.RepairConfigs()               ← 读配置
        ↓
.Build()                       ← 开门
        ↓
ServiceProvider = _host.Services  ← 把钥匙存起来
        ↓
以后用:
  var vm = ServiceProvider.GetRequiredService<MainShellVM>();

对比你现在的项目

总结:就是搭建一个"自动创建+自动注入"的零件工厂,用的时候直接拿,不用自己 new。