MAUI ListView设SaxonCS XdmValue为ItemsSource时栈溢出咨询
我尝试参照WPF ListView 的使用方式操作MAUI ListView:直接向ItemsSource属性赋值实现了IEnumerable接口、且集合内元素重写了ToString方法的对象,期望控件直接展示各元素的ToString()返回结果。
在WPF环境下该方式运行正常,无论数据源是.NET内置类型,还是SaxonCS库中的值,ListView均可正常展示项内容;但在MAUI环境下,该方式仅对简单string[]类型数组生效,当绑定SaxonCS库提供的XdmItem[]或XdmValue类型对象时,程序会抛出无详细堆栈跟踪的StackOverflow异常,仅提示“System.StackOverflowException in System.Private.CoreLib.dll”。
注:所有测试均在Windows平台完成,WPF本身仅支持Windows无需额外说明,本次排查仅关注MAUI的Windows端使用场景;复现问题需要SaxonCS的评估许可证,复现门槛较高。最小复现代码分别包含MAUI版本和WPF版本的工程,仅绑定
string[]数组的分支在两个框架下均可正常运行。
可正常运行的WPF示例XAML代码如下:
<Window x:Class="WpfXdmViewTest1.MainWindow" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" xmlns:local="clr-namespace:WpfXdmViewTest1" mc:Ignorable="d" Title="MainWindow" Height="450" Width="800"> <Grid> <ListView Margin="10" Name="XdmView"></ListView> </Grid> </Window>
对应后台代码如下:
public partial class MainWindow : Window { private static Processor processor = new Processor(); public MainWindow() { InitializeComponent(); //var values = new string[] { "foo", "bar", "baz" }; //var values = new XdmItem[] { new XdmAtomicValue("foo"), new XdmAtomicValue(3), new XdmAtomicValue(true) }; var xqueryCompiler = processor.NewXQueryCompiler(); xqueryCompiler.BaseUri = new Uri("urn:from-string"); var xqueryExecutable = xqueryCompiler.Compile("(1 to 5) ! <item>Item {.}</item>"); var xqueryEvaluator = xqueryExecutable.Load(); var xdmValue = xqueryEvaluator.Evaluate(); XdmView.ItemsSource = xdmValue; } }
上述WPF代码运行正常,可在ListView中展示每个XdmItem的ToString()结果。
结构几乎一致的MAUI示例XAML代码如下:
<ContentPage xmlns="http://schemas.microsoft.com/dotnet/2021/maui" xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml" x:Class="MAUIXdmViewTest1.MainPage"> <ListView x:Name="XdmView"></ListView> </ContentPage>
对应MAUI后台代码如下:
using Saxon.Api; public partial class MainPage : ContentPage { private static Processor processor = new Processor(); public MainPage() { InitializeComponent(); //var values = new string[] { "foo", "bar", "baz" }; //var values = new XdmItem[] { new XdmAtomicValue("foo"), new XdmAtomicValue(3), new XdmAtomicValue(true) }; var xqueryCompiler = processor.NewXQueryCompiler(); xqueryCompiler.BaseUri = new Uri("urn:from-string"); var xqueryExecutable = xqueryCompiler.Compile("(1 to 5) ! <item>Item {.}</item>"); var xqueryEvaluator = xqueryExecutable.Load(); var xdmValue = xqueryEvaluator.Evaluate(); XdmView.ItemsSource = xdmValue; } }
上述MAUI代码运行失败,会在System.Private.CoreLib.dll中触发栈溢出异常。
两者默认的项模板生成、对象访问逻辑完全不同,这是触发栈溢出的根本原因:
- WPF的ListView默认项模板逻辑非常简单:当没有自定义ItemTemplate时,会直接调用绑定项的
ToString()方法生成文本块展示,不会对绑定对象做额外的反射遍历,哪怕对象本身实现了IEnumerable或者有复杂嵌套属性,也不会递归访问子成员。 - MAUI的ListView(以及同框架下的CollectionView)默认项模板走的是反射式属性递归遍历逻辑:当绑定项不是.NET原生基础类型(比如string、int这类)时,MAUI会默认尝试反射读取对象的所有公共属性,尝试把复杂对象展开成可展示的结构。如果反射到的属性本身返回的是同类型对象、或者存在循环引用的属性链、或者属性返回的对象依然是复杂可枚举类型,就会无限递归反射下去,最终触发栈溢出。
SaxonCS的XdmValue/XdmItem类型本身就满足触发无限递归的条件:这类XDM对象的属性会返回其他XDM节点/值对象,部分属性甚至存在隐式的循环引用(比如XML节点的Parent属性返回父节点,父节点的Child属性又包含当前节点),MAUI默认反射逻辑撞到这种结构就会无限递归,直接栈溢出。string[]能正常运行是因为string属于MAUI识别的基础类型,不会触发后续反射遍历。
- 先验证栈溢出根因:不要直接把XdmValue赋值给ItemsSource,先手动把集合转成
List<string>,遍历XdmValue逐个调用ToString()把结果存到字符串列表里,再绑定给ListView。如果这种方式运行正常,就可以确认是MAUI默认反射遍历复杂对象导致的递归溢出。var displayList = xdmValue.Select(item => item.ToString()).ToList(); XdmView.ItemsSource = displayList; - 不想手动转集合就显式指定ItemTemplate切断反射逻辑:直接给ListView写死最简单的文本项模板,明确告诉MAUI只需要调用
ToString()展示文本,不要做额外反射。
这种写法和WPF的默认行为完全对齐,MAUI拿到明确的模板后就不会走默认的复杂对象反射展开逻辑,从根源避免递归。<ListView x:Name="XdmView"> <ListView.ItemTemplate> <DataTemplate> <TextCell Text="{Binding}" /> </DataTemplate> </ListView.ItemTemplate> </ListView> - 如果后续需要展示XdmItem的特定属性,不要依赖默认模板,手动在DataTemplate里绑定需要的具体属性即可,不要让MAUI自行反射解析对象结构。
内容的提问来源于stack exchange,提问作者Martin Honnen

