在XAML中绑定值类型的危险

The Old New Thing (Raymond Chen) 工具

摘要

这篇博客文章讨论了XAML中的一个技术陷阱,其中绑定到值类型会导致辅助工具的导航失败,原因在于值拷贝语义。文章建议使用运行时类和辅助库如wil来避免这个问题。

<p>一位同事在使用他们的XAML程序时遇到了问题。他们正在使用一个<code>FlipView</code>控件绑定到一个集合,但当用户尝试使用辅助技术工具导航<code>FlipView</code>时,出现了导航失败的情况。</p> <p>过了一段时间,他们带着谜团的解决方案回来了。</p> <p>团队注意到他们的数据模型只由字符串和其他值类型组成,因此他们决定将数据模型声明为<code>struct</code>而不是完整的<code>runtimeclass</code>,从而避免了大量样板代码编写。</p> <p>如果定义为<code>runtimeclass</code>:</p> <pre>// MyComponent.idl runtimeclass MyPageContent { String Title { get; }; String Description { get; }; String LinkUri { get; }; Boolean IsNew{ get; }; } // MyPageContent.h namespace winrt::MyComponent { struct MyPageContent : implements&lt;MyPageContent&gt; { MyPageContent(hstring const&amp; title, hstring const&amp; description, hstring const&amp; link, bool isNew) : m_title(title), m_description(description), m_link(link), m_isNew(isNew) {} hstring Title() const { return m_title; } hstring Description() const { return m_description; } hstring Link() const { return m_link; } bool IsNew() const { return m_isNew; } private: hstring m_title; hstring m_description; Windows::Foundation::Uri m_link; bool m_isNew; }; } // Consumer.cpp m_pages.Append(winrt::make&lt;MyPageContent&gt;( title, description, link, isNew)); </pre> <p>但如果定义为<code>struct</code>,则大部分代码就不需要了:</p> <pre>// MyComponent.idl <span style="border: solid 1px currentcolor;">struct</span> MyPageContent { String Title; String Description; String Link; Boolean IsNew; } // <span style="text-decoration: line-through;">MyPageContent.h</span> 不需要 // Consumer.cpp m_pages.Append(MyPageContent(title, description, link, isNew)); </pre> <p><a href="https://en.wikipedia.org/wiki/Miller_Lite#Advertising">口感好,更清爽</a>。</p> <p>现在,值类型之所以是值类型,在于它们是按值拷贝,而不是按引用拷贝。这意味着当XAML调用<code>GetAt(n)</code>从<code>m_pages</code>获取第<var>n</var>项时,它得到的是<code>MyPageContent</code>的一个<i>副本</i>,并绑定到该副本。</p> <p>而这就是问题的根源。</p> <p>当代码需要在辅助技术工具的请求下导航到特定项时,它传递要导航到的<code>MyPageContent</code>,但那只是另一个副本,因为值类型总是按副本传递。XAML说,“我没有那个家伙”,并导致导航失败。(XAML没有意识到它有一个<i>看起来完全像</i>那个家伙的家伙。不过这不重要,因为它不是同一个家伙。)</p> <p>这个巧妙的快捷方式结果成了问题所在。</p> <p>现在,虽然实现C++/WinRT运行时类确实需要大量代码编写,但有辅助工具可以减少所需代码量。在Windows Implementation Library (wil)中,<code>cppwinrt_authoring.h</code>头文件包含用于简化事件和属性实现的类。它利用了CRTP<a title="利用C++/WinRT CRTP:属性和事件声明" href="https://devblogs.microsoft.com/oldnewthing/20230317-00/?p=107946">,方式与我之前讨论的类似</a>。</p> <pre>// MyPageContent.h namespace winrt::MyComponent { struct MyPageContent : implements&lt;MyPageContent&gt; { MyPageContent(hstring const&amp; title, hstring const&amp; description, hstring const&amp; link, bool isNew) : m_title(title), m_description(description), m_link(link), m_isNew(isNew) {} <span style="border: solid 1px currentcolor; border-bottom: none;">wil::single_threaded_property&lt;hstring&gt; Title; </span> <span style="border: 1px currentcolor; border-style: none solid;">wil::single_threaded_property&lt;hstring&gt; Description;</span> <span style="border: 1px currentcolor; border-style: none solid;">wil::single_threaded_property&lt;hstring&gt; Link; </span> <span style="border: solid 1px currentcolor; border-top: none;">wil::single_threaded_property&lt;bool&gt; IsNew; </span> }; } </pre> <p>我们可以使用<code>single_<wbr />threaded_<wbr />property</code>,因为属性仅在构造时写入,因此并发读取不会引起问题。</p> <p>文章<a href="https://devblogs.microsoft.com/oldnewthing/20260902-00/?p=112668">在XAML中绑定值类型的危险</a>首次出现在<a href="https://devblogs.microsoft.com/oldnewthing">The Old New Thing</a>。</p>
查看原文
查看缓存全文

缓存时间: 2026/09/03 11:43

# 在XAML中绑定值类型的陷阱 - Old New Thing专栏 来源:https://devblogs.microsoft.com/oldnewthing/20260902-00?p=112668 一位同事在他们的XAML程序中遇到了问题。他们正在使用`FlipView`控件绑定到一个集合,但当用户尝试使用辅助技术工具导航`FlipView`时,出现了导航失败的情况。一段时间后,他们带着这个谜题的解决方案回来了。 该团队注意到他们的数据模型只由字符串和其他值类型组成,因此他们决定将数据模型声明为`struct`(结构体)而非完整的`runtimeclass`(运行时类),从而避免了大量样板式代码编写。如果定义为`runtimeclass`: ``` // MyComponent.idl runtimeclass MyPageContent { String Title { get; }; String Description { get; }; String LinkUri { get; }; Boolean IsNew{ get; }; } // MyPageContent.h namespace winrt::MyComponent { struct MyPageContent : implements<MyPageContent, IPageContent> { MyPageContent(hstring const& title, hstring const& description, hstring const& link, bool isNew) : m_title(title), m_description(description), m_link(link), m_isNew(isNew) {} hstring Title() const { return m_title; } hstring Description() const { return m_description; } hstring Link() const { return m_link; } bool IsNew() const { return m_isNew; } private: hstring m_title; hstring m_description; Windows::Foundation::Uri m_link; bool m_isNew; }; } // Consumer.cpp m_pages.Append(winrt::make<MyPageContent>(title, description, link, isNew)); ``` 但如果将其定义为`struct`,则大部分代码都变得不必要了: ``` // MyComponent.idl struct MyPageContent { String Title; String Description; String Link; Boolean IsNew; } // MyPageContent.h 不再需要 // Consumer.cpp m_pages.Append(MyPageContent{title, description, link, isNew}); ``` 这可谓“口感更佳,负担更轻”(Miller Lite经典广告词)。现在,使值类型成为值类型的关键在于它们是按值复制的,而非按引用复制。这意味着当XAML调用`m_pages`上的`GetAt(n)`来获取第n个项目时,它获得的是`MyPageContent`的*副本*,并绑定到该副本。而这就是问题的根源。当代码需要根据辅助技术工具的请求导航到特定项目时,它传递的是要导航到的`MyPageContent`,但由于值类型总是按副本传递,这仅仅是另一个副本。XAML表示:“我没有那个家伙”,导航因此失败。(XAML没有意识到它拥有一个*看起来与那个家伙完全相同*的家伙。但这并不重要,因为它并非同一个家伙。) 这个巧妙的捷径最终被证明是问题所在。虽然实现C++/WinRT运行时类确实需要大量代码编写,但也有工具可以减少所需的代码量。在Windows实现库(WIL)中,`cppwinrt_authoring.h`头文件包含用于简化事件和属性实现的类。它利用CRTP(奇异递归模板模式)的方式与我之前讨论(https://devblogs.microsoft.com/oldnewthing/20230317-00/?p=107946)的相同。 ``` // MyPageContent.h namespace winrt::MyComponent { struct MyPageContent : implements<MyPageContent, IPageContent> { MyPageContent(hstring const& title, hstring const& description, hstring const& link, bool isNew) : m_title(title), m_description(description), m_link(link), m_isNew(isNew) {} wil::single_threaded_property<hstring> Title{m_title}; wil::single_threaded_property<hstring> Description{m_description}; wil::single_threaded_property<Windows::Foundation::Uri> Link{m_link}; wil::single_threaded_property<bool> IsNew{m_isNew}; }; } ``` 我们可以使用`single_threaded_property`,因为这些属性仅在构造时写入,因此并发读取不会引起问题。 ### 分类 ### 主题 ## 作者 Raymond Chen Raymond参与Windows的演进已有30余年。2003年,他创办了一个名为"The Old New Thing"的网站,其受欢迎程度远远超出他最疯狂的想象,这一发展至今仍让他感到不安。该网站催生了一本书,书名也恰巧叫做《The Old New Thing》(Addison Wesley出版社,2007年)。他偶尔会出现在Windows开发文档的Twitter账号上,讲述一些并无实用信息的故事。

相似文章

Vision-Language Models are Fragile Multilingual Associators

arXiv cs.CL

This paper introduces M2BIND, a benchmark to evaluate whether vision-language models maintain stable visual-linguistic associations across languages. It finds that binding is not language-invariant, with cross-family and cross-script settings causing significant performance collapse and weaker internal causal binding.