记录一次附加属性失效全过程

1、我为了实现View到ViewModel的自动绑定,写了附加属性

local:ViewModelSelector.AutoWireViewModel="True"

没想到它不触发,我将dll的触发属性迁移到本地项目,触发了,但是报错MainViewModel找不到Text
2、我的xaml构造如下
MainView:

<Grid>
    <Grid.ColumnDefinitions>
        <ColumnDefinition />
        <ColumnDefinition />
    </Grid.ColumnDefinitions>

    <Button Width="200" Content="{Binding Name}" />

    <ContentControl Grid.Column="1" Content="{Binding View}" />
</Grid>

HomeView:

 td:ViewModelSelector.AutoWireViewModel="True"
<StackPanel>
    <TextBlock Text="{Binding Text}" />
</StackPanel>

此时发现附加属性触发,但是viewModelType为null,
打断点尝试,发现ViewModel没有注册,
注册完成后运行,失败,原来在同一目录去查找了
继续修改,将View和ViewModel替换

var name = view.GetType().FullName.Replace("View", "ViewModel");
var viewModelType = Type.GetType(name);
if (viewModelType != null)
{
    var viewModel = TangdaoApplication.Provider.GetService(viewModelType);
    view.DataContext = viewModel;
}

继续运行,
界面已经能出现 “World” → HomeView.DataContext 确实是 HomeViewModel,绑定完全正确。
但 VS 依旧报 “MainViewModel 上找不到 Text” → 说明有一处绑定表达式并没有指向 HomeViewModel,而是指到了 MainViewModel;运行期只是碰巧在 HomeView 区域看见了正确的值
此时我使用PresentationTraceSources.TraceLevel=High炸出所有报错
我怀疑附加属性运行了两次
我使用

 view.ClearValue(FrameworkElement.DataContextProperty); 

切断继承链并延迟到Initialized去触发,成功了
成功,此时打包代码到dll,继续使用dll执行。
把完全相同的代码从 EXE 项目搬到 DLL 后,又出现 “MainViewModel 找不到 Text”
没触发???为此,我对dll的附加属性类所有行打上断点
继续运行
还是没触发???
我添加代码

 public HomeView()
 {
     InitializeComponent();
     bool flag = IT.Tangdao.Framework.Selectors.ViewModelSelector.GetAutoWireViewModel(this);
     System.Diagnostics.Debug.WriteLine($"[HomeView] AutoWireViewModel={flag}");
 }

是true,但是为什么不进断点
为什么附加属性不触发,我使用System.Diagnostics.Debugger.Break();强制进断点
可以进去,现在我怀疑是JIT问题

 // 1. 显式静态构造
 static ViewModelSelector()
 {
     // 2. 强制触摸字段,防止 JIT 死代码消除
     var dummy = AutoWireViewModelProperty;
 }

还是不起作用出现 “MainViewModel 找不到 Text”
上火了,我将能打断点的可疑地方全部上断点
直到

  private HomeView _view;

  public HomeView View
  {
      get => _view;
      set => SetProperty(ref _view, value);
  }

get方法卡死,导致VS2022卡出去,吆喝,来活了
我现在怀疑HomeView 实例并不是由 XAML 解析器创建的,

 View = TangdaoApplication.Provider.GetService<HomeView>();

我检查我的IOC容器,测压多次应该没问题,那么我对IOC容器解析没问题
但是以防万一,我将解析改为new,new HomeView()
还是卡死,此时
我改为从ViewModel先行

 private HomeViewModel _view;

 public HomeViewModel View
 {
     get => _view;
     set => SetProperty(ref _view, value);
 }
  View = TangdaoApplication.Provider.GetService<HomeViewModel>();

加上数据模板

  <DataTemplate DataType="{x:Type vm:HomeViewModel}">
      <view:HomeView />
  </DataTemplate>

此时不报错,界面成功出现数据

这是因为
代码 new 视图 + SetProperty 触发属性通知 → 设计器反复创建 → 递归死循环
这是 View-First 最典型的“设计时炸弹”,无论 new HomeView() 还是 IOC 解析 HomeView,只要视图由代码实例化并塞进属性通知链路,都会中招;
ViewModel-First 天然免疫,因为 XAML 解析器只在需要呈现时才创建视图,永远不会递归触发属性通知。

阶段 环境 现象 关键发现
初期调试 EXE 项目 附加属性不触发 代码在本地可调试
中期迁移 DLL 迁移 附加属性完全失效 静态构造函数可能被优化
架构调整 View-First 设计时递归卡死 new HomeView() 导致属性通知循环
最终方案 ViewModel-First 运行正常 数据模板 + IOC 容器解析
维度 View-First 模式 ViewModel-First 模式
实例化时机 代码中显式 new 或 IOC 解析 XAML 解析器按需创建
设计时支持 容易递归卡死 VS 天然免疫设计时问题
数据绑定 容易受继承污染 数据模板明确关联
内存管理 可能重复创建视图 按需创建,生命周期清晰
调试难度 高(属性通知循环) 低(清晰的创建链路)

最后总结一句话
“在 WPF 里,视图 new 得越少,世界越干净;
把 new 交给 XAML,把逻辑交给 VM,把断点留给自己。”

posted @ 2025-09-24 06:31  孤沉  阅读(12)  评论(0)    收藏  举报