[{"data":1,"prerenderedAt":320},["ShallowReactive",2],{"page-\u002Fcsharp\u002Fwpf\u002F解包":3},{"id":4,"title":5,"body":6,"description":12,"extension":313,"meta":314,"navigation":315,"path":316,"seo":317,"stem":318,"__hash__":319},"content\u002Fcsharp\u002FWPF\u002F解包.md","解包",{"type":7,"value":8,"toc":311},"minimark",[9,13,120,128,131,134,137,151,171,174,188,214,236,239,246,253,256,307],[10,11,12],"p",{},"编写WPF的时候，使用CallMethodAction去通过反射调用对应的方法的时候，遇到了一个问题，那就是，在TargetObject中是不能使用Binding去绑定对象的。",[14,15,20],"pre",{"className":16,"code":17,"language":18,"meta":19,"style":19},"language-xml shiki shiki-themes github-light github-dark","    \u003CWindow.Resources>\n        \u003CObjectDataProvider x:Key=\"Ticks\" ObjectType=\"{x:Type linq:Enumerable}\" MethodName=\"Range\">\n            \u003CObjectDataProvider.MethodParameters>\n                \u003Csys:Int32>1\u003C\u002Fsys:Int32>\n                \u003Csys:Int32>12\u003C\u002Fsys:Int32>\n            \u003C\u002FObjectDataProvider.MethodParameters>\n        \u003C\u002FObjectDataProvider>\n        \u003CObjectDataProvider x:Key=\"CurrentTime\" ObjectType=\"{x:Type sys:DateTime}\" MethodName=\"get_Now\">\n        \u003C\u002FObjectDataProvider>\n    \u003C\u002FWindow.Resources>\n    \n    \u003Cbehaviors:Interaction.Triggers>\n        \u003Cbehaviors:TimerTrigger MillisecondsPerTick=\"1000\">\n            \u003Cbehaviors:CallMethodAction MethodName=\"Rereash\" TargetObject=\"{StaticResource CurrentTime}\"\u002F>\n        \u003C\u002Fbehaviors:TimerTrigger>\n    \u003C\u002Fbehaviors:Interaction.Triggers>\n\n","xml","",[21,22,23,31,37,43,49,55,61,67,73,78,84,90,96,102,108,114],"code",{"__ignoreMap":19},[24,25,28],"span",{"class":26,"line":27},"line",1,[24,29,30],{},"    \u003CWindow.Resources>\n",[24,32,34],{"class":26,"line":33},2,[24,35,36],{},"        \u003CObjectDataProvider x:Key=\"Ticks\" ObjectType=\"{x:Type linq:Enumerable}\" MethodName=\"Range\">\n",[24,38,40],{"class":26,"line":39},3,[24,41,42],{},"            \u003CObjectDataProvider.MethodParameters>\n",[24,44,46],{"class":26,"line":45},4,[24,47,48],{},"                \u003Csys:Int32>1\u003C\u002Fsys:Int32>\n",[24,50,52],{"class":26,"line":51},5,[24,53,54],{},"                \u003Csys:Int32>12\u003C\u002Fsys:Int32>\n",[24,56,58],{"class":26,"line":57},6,[24,59,60],{},"            \u003C\u002FObjectDataProvider.MethodParameters>\n",[24,62,64],{"class":26,"line":63},7,[24,65,66],{},"        \u003C\u002FObjectDataProvider>\n",[24,68,70],{"class":26,"line":69},8,[24,71,72],{},"        \u003CObjectDataProvider x:Key=\"CurrentTime\" ObjectType=\"{x:Type sys:DateTime}\" MethodName=\"get_Now\">\n",[24,74,76],{"class":26,"line":75},9,[24,77,66],{},[24,79,81],{"class":26,"line":80},10,[24,82,83],{},"    \u003C\u002FWindow.Resources>\n",[24,85,87],{"class":26,"line":86},11,[24,88,89],{},"    \n",[24,91,93],{"class":26,"line":92},12,[24,94,95],{},"    \u003Cbehaviors:Interaction.Triggers>\n",[24,97,99],{"class":26,"line":98},13,[24,100,101],{},"        \u003Cbehaviors:TimerTrigger MillisecondsPerTick=\"1000\">\n",[24,103,105],{"class":26,"line":104},14,[24,106,107],{},"            \u003Cbehaviors:CallMethodAction MethodName=\"Rereash\" TargetObject=\"{StaticResource CurrentTime}\"\u002F>\n",[24,109,111],{"class":26,"line":110},15,[24,112,113],{},"        \u003C\u002Fbehaviors:TimerTrigger>\n",[24,115,117],{"class":26,"line":116},16,[24,118,119],{},"    \u003C\u002Fbehaviors:Interaction.Triggers>\n",[10,121,122,123,127],{},"主要的思想涉及到解包，因为StaticResource给的是对象本身，但是Binding默认给的是包装后的",[124,125,126],"strong",{},"数据上下文","。",[10,129,130],{},"当我们使用{StaticResource}的时候，XAML解析器会去资源字典吧CurrentTime对象给返回给TargetObject。因此此时的TargetObject就是我们的——。反射引擎可以直接在他身上找到Refresh方法，",[10,132,133],{},"而我们使用{Binding}，由于Binding是一个异步的有生命周期的观察者，因此TargetObject拿到的是Binding对象或者它还没有解析出来的Value。而CallMethodAction在初始化的时候就会去尝试匹配方法，因此就会反射失败，就会报错。",[10,135,136],{},"如果来深度解析一下！",[10,138,139,142,143,146,147,150],{},[21,140,141],{},"CallMethodAction"," 的 ",[21,144,145],{},"TargetObject"," 是一个 ",[124,148,149],{},"DependencyProperty (依赖属性)","，而静态绑定在编译期间或者加载期间就会确定引用，是强类型的直接指向。",[10,152,153,156,157,160,161,164,165,167,168,127],{},[21,154,155],{},"Binding"," 依赖于 ",[21,158,159],{},"DataContext","。如果 ",[21,162,163],{},"TimerTrigger"," 所在的 UI 元素还没有完全挂载到逻辑树上，",[21,166,159],{}," 可能是 ",[21,169,170],{},"null",[10,172,173],{},"更加进一步的思考：\n为啥不一开始就可以弄成反射消除的静态实现呢？",[10,175,176,177,180,181,183,184,187],{},"这主要因为",[21,178,179],{},"Interaction.Triggers"," 和 ",[21,182,141],{}," 并不是 WPF 核心框架的一部分，它们属于 ",[124,185,186],{},"Microsoft.Xaml.Behaviors"," 库（以前叫 Blend SDK）。",[10,189,190,191,194,195,198,199,202,203,206,207,210,211,127],{},"WPF的编译器MSBuild中的MarkupCompilePass负责处理核心标签（如 ",[21,192,193],{},"Grid",", ",[21,196,197],{},"Button","）。有一些黑盒行为，编译器在编译时",[124,200,201],{},"只把它们当成普通的类","。它并不知道 ",[21,204,205],{},"MethodName=\"Refresh\""," 里的字符串指向的是",[124,208,209],{},"哪个具体的 C# 函数","，因此只能",[124,212,213],{},"留到运行时（Runtime）去用反射“找”这个方法",[10,215,216,217,220,221,223,224,227,228,231,232,235],{},"还有一部分原因，C#是强类型的，但是传统XAML是基于字符串的，因此当你写 ",[21,218,219],{},"{Binding ...}"," 时，编译器并不知道这个 ",[21,222,159],{}," 到时候会是一个 ",[21,225,226],{},"ClockViewModel"," 还是一个 ",[21,229,230],{},"UserViewModel","。\n既然编译器不知道对象的“真身”，它就没法提前生成 ",[21,233,234],{},"CurrentTime.Refresh()"," 这样的硬编码调用，只能通过反射在运行时询问",[10,237,238],{},"扩展",[10,240,241,242,245],{},"在现代框架比如说Avalonia或者MAUI中，已经支持了消除反射的工作。也就是x:Bind，它在编译的时候会生成C#代码，如果你路径写错了，",[124,243,244],{},"编译就会报错","，而不是运行到一半才崩溃。原理就是显式指定x:DataType，让编译器去类里寻找。",[247,248,250],"h4",{"id":249},"方案改用事件订阅event-driven",[124,251,252],{},"方案：改用事件订阅（Event-driven）",[10,254,255],{},"不要让 XAML 去通过字符串找方法，直接在 C# 里把逻辑接通：",[14,257,261],{"className":258,"code":259,"language":260,"meta":19,"style":19},"language-csharp shiki shiki-themes github-light github-dark","\u002F\u002F 在 MainWindow.xaml.cs 中\npublic MainWindow() {\n    InitializeComponent();\n    \n    \u002F\u002F 定时器直接调用方法，完全不经过 XAML 反射\n    var timer = new DispatcherTimer { Interval = TimeSpan.FromSeconds(1) };\n    timer.Tick += (s, e) => (this.Resources[\"CurrentTime\"] as MyClock)?.Refresh();\n    timer.Start();\n}\n","csharp",[21,262,263,268,273,278,282,287,292,297,302],{"__ignoreMap":19},[24,264,265],{"class":26,"line":27},[24,266,267],{},"\u002F\u002F 在 MainWindow.xaml.cs 中\n",[24,269,270],{"class":26,"line":33},[24,271,272],{},"public MainWindow() {\n",[24,274,275],{"class":26,"line":39},[24,276,277],{},"    InitializeComponent();\n",[24,279,280],{"class":26,"line":45},[24,281,89],{},[24,283,284],{"class":26,"line":51},[24,285,286],{},"    \u002F\u002F 定时器直接调用方法，完全不经过 XAML 反射\n",[24,288,289],{"class":26,"line":57},[24,290,291],{},"    var timer = new DispatcherTimer { Interval = TimeSpan.FromSeconds(1) };\n",[24,293,294],{"class":26,"line":63},[24,295,296],{},"    timer.Tick += (s, e) => (this.Resources[\"CurrentTime\"] as MyClock)?.Refresh();\n",[24,298,299],{"class":26,"line":69},[24,300,301],{},"    timer.Start();\n",[24,303,304],{"class":26,"line":75},[24,305,306],{},"}\n",[308,309,310],"style",{},"html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html.dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}",{"title":19,"searchDepth":33,"depth":33,"links":312},[],"md",{},true,"\u002Fcsharp\u002Fwpf\u002F解包",{"description":12},"csharp\u002FWPF\u002F解包","ZG20CzX_iDrQv6uWQorgO-2UPw1dE7TczR3itoOsAJc",1791042466513]