问题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。