C# Xamarin:代码封装到DLL后点击按钮抛出空引用异常
问题根因
你以为抛出的是NullReferenceException,实际从异常堆栈首行就能看到,真正的底层异常是System.TypeLoadException。这个异常发生在点击事件方法被JIT编译、还没进入你写的方法逻辑之前,所以你在方法内部加的全链路空值校验、try/catch块完全没有生效机会,根本捕获不到。
这个问题仅在封装为独立DLL时出现,核心诱因有两个:
- 两个按钮点击事件的方法签名不一致:
plusClicked是静态方法,minusClicked是实例方法。代码直接写在主项目时,编译器会自动做签名兼容处理,不会出问题;但编译为独立类库DLL后,Xamarin.Forms运行时在Android端解析事件处理器委托时,会因为静态/实例签名不匹配触发类型加载失败。 - 类库和主项目的Xamarin.Forms相关NuGet包版本存在小版本差异,也是类库场景下TypeLoadException的常见诱因。你靠硬编码可视化树层级(遍历Parent、靠Children索引找控件)的写法鲁棒性很差,属于次要隐患,不是这次异常的直接原因。
修复方案
- 统一两个点击事件的签名,全部改为实例方法,不要用静态方法直接绑定UI事件,同时优化控件查找逻辑,不要靠硬编码索引、Parent遍历找关联控件:
public void plusClicked(object sender, EventArgs e) { try { if (sender is not Button button || button.Parent is not Grid grid) return; var entry = grid.Children.OfType<Entry>().FirstOrDefault(); if (entry == null || !int.TryParse(entry.Text, out int currentValue)) return; entry.Text = (currentValue + 1).ToString(); } catch (Exception ex) { Console.WriteLine($"加按钮点击异常: {ex}"); } } public void minusClicked(object sender, EventArgs e) { try { if (sender is not Button button || button.Parent is not Grid grid) return; var entry = grid.Children.OfType<Entry>().FirstOrDefault(); if (entry == null || !int.TryParse(entry.Text, out int currentValue)) return; entry.Text = (currentValue - 1).ToString(); } catch (Exception ex) { Console.WriteLine($"减按钮点击异常: {ex}"); } }
- 对齐类库项目和主项目的所有Xamarin相关NuGet包版本,包括
Xamarin.Forms、各平台渲染器相关依赖包,完全消除版本差。 - 更稳妥的控件实现方式是创建控件时直接通过闭包捕获Entry引用,完全不依赖可视化树层级查找,从根源避免层级变动、事件签名问题:
public View createIntegerControl(String name, String value, int DatabaseId = 0) { StackLayout newStackLayout = new StackLayout(); newStackLayout.SetValue(AttachableProperties.DatabaseIdProperty, DatabaseId); Label label = new Label() { Text = name }; Grid inputFields = new Grid(); inputFields.ColumnDefinitions.Add(new ColumnDefinition{ Width = new GridLength(70, GridUnitType.Star) }); inputFields.ColumnDefinitions.Add(new ColumnDefinition{ Width = new GridLength(15, GridUnitType.Star) }); inputFields.ColumnDefinitions.Add(new ColumnDefinition{ Width = new GridLength(15, GridUnitType.Star) }); Entry entry = new Entry() { Text = value }; entry.SetValue(Grid.ColumnProperty, 0); Button plus = new Button() { Text = "+" }; plus.SetValue(Grid.ColumnProperty, 1); // 直接闭包捕获当前entry实例,无需运行时查找 plus.Clicked += (s, args) => { if (int.TryParse(entry.Text, out int val)) entry.Text = (val + 1).ToString(); }; Button minus = new Button() { Text = "-" }; minus.SetValue(Grid.ColumnProperty, 2); minus.Clicked += (s, args) => { if (int.TryParse(entry.Text, out int val)) entry.Text = (val - 1).ToString(); }; newStackLayout.Children.Add(label); inputFields.Children.Add(entry); inputFields.Children.Add(plus); inputFields.Children.Add(minus); newStackLayout.Children.Add(inputFields); return newStackLayout; }
提示:如果用闭包绑定事件,记得在页面销毁时手动解除事件绑定,避免内存泄漏。
补充说明
你看到的NullReferenceException是Android JNI层包装未处理异常时抛出的误导性报错,不是你的业务代码里真的有空值,别在空值校验上浪费时间。
内容的提问来源于stack exchange,提问作者Valvatorez
相关产品推荐
相关产品推荐

