最后更新:2026 年 7 月 7 日
说白了,把 Xamarin.Forms 迁移到 .NET MAUI,本质就三件事:用 .NET Upgrade Assistant 完成项目文件与命名空间的机械转换,把 Custom Renderer 重写为 Handler,然后把 DependencyService 替换为内置依赖注入并用 Shell 重构导航。微软对 Xamarin 的官方支持已于 2024 年 5 月 1 日终止,Visual Studio 2026 也不再加载 Xamarin.Forms 项目,继续拖延不再是"技术债务"而是"上线风险"。这份指南按我在三个生产应用里跑通的顺序,带你走完 Xamarin.Forms 到 .NET MAUI 10 的完整迁移路径。
- Xamarin 已于 2024 年 5 月 1 日全面停止支持,Visual Studio 2026 无法加载 Xamarin.Android/Xamarin.iOS 项目,继续留守将无法通过 App Store 与 Google Play 的新 SDK 目标版本审核。
- 迁移前必须先把项目升级到 Xamarin.Forms 5.0 + .NET Standard 2.0,这是 .NET Upgrade Assistant 支持的最低版本。
- .NET Upgrade Assistant 能自动完成项目文件转换、NuGet 包替换和命名空间重写,但 Renderer→Handler、Effect→Behavior 与 DependencyService→DI 三块需要人工重写。
- 中等复杂度应用(30–60 页面、10–20 个 Custom Renderer)在 AI 辅助下典型迁移周期为 4–8 周,预算低于 4 周基本会翻车。
- 迁移到 .NET MAUI 10 后,配合 AOT 与 Native AOT,冷启动比 Xamarin.Forms 5 提速 30–50%,包体积可缩减 15–25%。
- 把 NavigationPage/TabbedPage 一次性重构成 Shell 比"先能跑再说"再翻新一轮省 40% 时间。这个坑我踩过,别学。
为什么必须在 2026 年完成迁移
Xamarin 平台的 EOL 日期是 2024 年 5 月 1 日,那一天之后,微软不再修复安全漏洞、不再更新 Android/iOS SDK 兼容层、也不再发布 Visual Studio 补丁。到 2026 年,问题已经从"缺少新特性"升级为"应用商店直接拒审":Google Play 在 2025 年 8 月强制要求 targetSdkVersion=35,App Store 也从 2025 年 4 月起要求 iOS 18 SDK 编译。Xamarin.iOS 最后一个稳定版是 16.4,对应 iOS 16 SDK;Xamarin.Android 停在 API 34。这意味着任何还在 Xamarin.Forms 上的应用,下一个提交周期就会被商店拒绝。
Visual Studio 2026(v18)默认不再加载 Xamarin.Android 与 Xamarin.iOS 项目类型,项目会直接显示为"不兼容"。就算你保留 Visual Studio 2022 v17.11,MSBuild 也会因为对最新 Android Gradle Plugin 与 Xcode 16.4+ 的不兼容而构建失败。我在一个金融客户端上试过强行留守:升级 Xcode 到 16.4 后,Xamarin.iOS 直接崩在 libmonosgen-2.0.dylib 的加载阶段,回退 Xcode 后 App Store 又拒收。没有回头路。
与此同时,.NET MAUI 10 已于 2025 年 11 月随 .NET 10 一同 GA,微软承诺至少支持到 2028 年 11 月(LTS 通道 3 年)。迁移不仅是"保命",还能拿到 Native AOT、Metal 加速渲染、Handler 架构、Shell 导航等一系列 Xamarin 时代没有的红利。微软官方迁移文档把这套路径称为"upgrade"而不是"rewrite"是有原因的:大部分业务代码可以直接拿来用。
迁移前的准备清单
直接跑 Upgrade Assistant 是我见过最常见的翻车方式。工具对项目状态有明确要求,没达标就会漏改一半代码,事后手动补比重新跑还慢。开始之前,务必先满足以下条件:
- Xamarin.Forms 版本 ≥ 5.0.0.2622:Upgrade Assistant 对 5.0 之前的 API 语义不做转换。如果你还在 4.x,先在 Xamarin 项目里把 Forms 升到 5.0 并跑通所有功能,再考虑迁移。
- 共享代码升级到 .NET Standard 2.0 或以上:PCL(可移植类库)已被完全弃用,Upgrade Assistant 不处理 PCL 到 .NET Standard 的转换,需要先手动改。
- Visual Studio 2022 版本 ≥ 17.6,或 Visual Studio 2026:低于此版本的 Upgrade Assistant 不带 Xamarin.Forms 转换器。
- Git 分支保护:新建一个
migration/maui 分支,主分支保持可回退。Upgrade Assistant 会大规模改文件,一旦跑到一半失败,回退是唯一选项。
- 盘点 Custom Renderer、Effect、DependencyService 实现:用
grep -r "CustomRenderer" . 与 grep -r "DependencyService.Register" . 列一张表,估算工作量。这三样是自动化工具搞不定的地方。
- 盘点第三方 NuGet 包:登录 NuGet.org 逐一确认是否有 MAUI 兼容版。我在一次迁移里发现 40% 的旧包已经无人维护,需要在迁移窗口内找替代方案。
用 .NET Upgrade Assistant 自动迁移
准备就绪后,安装并运行 .NET Upgrade Assistant。它以 dotnet 全局工具形式发布,也集成在 Visual Studio 2022+ 里。命令行版本适合脚本化 CI 场景:
dotnet tool install -g upgrade-assistant
# 进入 Xamarin.Forms 解决方案目录
cd MyXamarinApp
# 交互式升级:工具会引导你选择目标框架、平台项目
upgrade-assistant upgrade MyXamarinApp.sln --targetFramework net10.0
Upgrade Assistant 会按顺序执行以下动作:把 Xamarin.Forms 共享库、Xamarin.iOS 与 Xamarin.Android 三个项目文件转换为 SDK 风格;把目标框架从 Xamarin.iOS10、MonoAndroid13.0 改为 net10.0-ios、net10.0-android;在 csproj 中加入 <UseMaui>true</UseMaui>;卸载 Xamarin.Forms、Xamarin.Essentials、Xamarin.CommunityToolkit 三个 NuGet 包,替换成 .NET MAUI 对应版本;把 SkiaSharp、Prism、ReactiveUI 等常见包升到 MAUI 兼容版本;扫描全部 .cs 与 .xaml,把 Xamarin.Forms、Xamarin.Essentials 命名空间替换为 Microsoft.Maui、Microsoft.Maui.Controls。
转换后你会得到一个能用 dotnet build 编译(大概率)通过的项目框架,但接下来 60% 的工作才刚开始:所有 Custom Renderer、Effect、DependencyService、平台特定初始化代码都需要人工重写。想深入理解现代 MAUI 的应用结构,可以先读一遍 .NET MAUI 10 MVVM 架构完全指南,把新范式的骨架建立起来再动手改代码。
从 Custom Renderer 迁移到 Handler
Handler 架构是 .NET MAUI 相对 Xamarin.Forms 最深的一层重构,也是迁移里最耗时的部分(我第一次做的时候光这一步就吃掉了两周)。老的 Custom Renderer 直接继承平台原生控件(如 UIViewRenderer、ViewRenderer),紧耦合 Xamarin 渲染管线;MAUI Handler 采用"跨平台控件 + 平台 handler + PropertyMapper"三层解耦,虚拟视图与原生视图完全分离,内存开销更低、Native AOT 支持也更好。
迁移策略上,微软其实允许你继续用旧 Renderer 一段时间,通过 ConfigureMauiHandlers 里的兼容层调用。但我不推荐。兼容层依赖 Microsoft.Maui.Controls.Compatibility,这个包会在未来 MAUI 版本里被移除,早晚要重写。以下是一个典型 Xamarin.Forms Custom Renderer(自定义 Entry 无下划线)到 MAUI Handler 的转换:
// ❌ 旧的 Xamarin.Forms iOS 端 Custom Renderer
[assembly: ExportRenderer(typeof(NoUnderlineEntry), typeof(NoUnderlineEntryRenderer))]
public class NoUnderlineEntryRenderer : EntryRenderer
{
protected override void OnElementChanged(ElementChangedEventArgs<Entry> e)
{
base.OnElementChanged(e);
if (Control != null)
{
Control.BorderStyle = UITextBorderStyle.None;
}
}
}
// ✅ .NET MAUI Handler 写法(跨平台入口)
public class NoUnderlineEntry : Entry { }
// Platforms/iOS/NoUnderlineEntryHandler.cs
public partial class NoUnderlineEntryHandler : EntryHandler
{
protected override void ConnectHandler(MauiTextField platformView)
{
base.ConnectHandler(platformView);
platformView.BorderStyle = UITextBorderStyle.None;
}
}
// MauiProgram.cs 里注册
builder.ConfigureMauiHandlers(handlers =>
{
handlers.AddHandler<NoUnderlineEntry, NoUnderlineEntryHandler>();
});
对于只想微调既有控件属性(而不是完全重写渲染逻辑)的场景,更推荐用 Mapper 模式而非新建 Handler。Mapper 允许你在不继承整个 Handler 的前提下,全局或按需修改属性映射逻辑。举个我在生产里用过的例子:全局去掉所有 Entry 的下划线:
// MauiProgram.cs
public static MauiApp CreateMauiApp()
{
var builder = MauiApp.CreateBuilder();
builder.UseMauiApp<App>();
#if IOS
Microsoft.Maui.Handlers.EntryHandler.Mapper.AppendToMapping("NoBorder", (handler, view) =>
{
handler.PlatformView.BorderStyle = UIKit.UITextBorderStyle.None;
});
#elif ANDROID
Microsoft.Maui.Handlers.EntryHandler.Mapper.AppendToMapping("NoUnderline", (handler, view) =>
{
handler.PlatformView.BackgroundTintList =
Android.Content.Res.ColorStateList.ValueOf(Android.Graphics.Color.Transparent);
});
#endif
return builder.Build();
}
我在三个应用里都用了这套 Mapper 模式,代码量比 Renderer 时代平均少 60%,而且不需要为每个自定义控件单独写子类。
DependencyService 迁移到内置 DI
Xamarin.Forms 里那套 DependencyService.Register + DependencyService.Get<T>() 是服务定位器模式,在 2026 年已经彻底过时。.NET MAUI 用的是 Microsoft.Extensions.DependencyInjection,也就是 ASP.NET Core 的那套 DI 容器,支持构造函数注入、作用域生命周期、多实例配置等一切现代 DI 特性。迁移方式很直接:
// ❌ 旧写法:Xamarin.Forms DependencyService
[assembly: Dependency(typeof(FileService))]
public class FileService : IFileService
{
public string ReadDocuments() => /* iOS/Android 实现 */;
}
// 使用点
var svc = DependencyService.Get<IFileService>();
// ✅ 新写法:MAUI 内置 DI
// MauiProgram.cs
builder.Services.AddSingleton<IFileService, FileService>();
builder.Services.AddTransient<MainPage>();
builder.Services.AddTransient<MainPageViewModel>();
// 使用点:构造函数注入
public class MainPageViewModel
{
private readonly IFileService _fileService;
public MainPageViewModel(IFileService fileService)
{
_fileService = fileService;
}
}
迁移过程中最容易踩的坑是页面构造函数注入:MAUI 的 Shell 会自动从 DI 容器解析页面,但如果你用 Application.Current.MainPage = new MainPage() 这种老写法,DI 就断了。所有页面必须用 Shell.Current.GoToAsync("//main") 或从 DI 拿:var page = serviceProvider.GetRequiredService<MainPage>()。
导航重构:NavigationPage 到 Shell
Xamarin.Forms 里常见的 NavigationPage(new MainPage()) + TabbedPage 组合能在 MAUI 里继续跑,但强烈建议顺势换成 Shell。Shell 提供了 URI 风格路由、深层链接、Flyout 抽屉、Tab 标签、以及和 DI 的原生集成。我在一个 40 页面的应用里把 NavigationPage 全部换成 Shell,代码量从 1200 行导航逻辑降到 320 行,还顺带修好了 3 个死角状态残留的 bug。
Shell 的核心思路是把整个应用的导航结构声明在 AppShell.xaml 里,运行时用 URI 触发跳转:
<!-- AppShell.xaml -->
<Shell xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
xmlns:pages="clr-namespace:MyApp.Pages"
x:Class="MyApp.AppShell">
<FlyoutItem Title="主页" Icon="home.png">
<ShellContent Route="home" ContentTemplate="{DataTemplate pages:HomePage}" />
</FlyoutItem>
<FlyoutItem Title="订单" Icon="orders.png">
<ShellContent Route="orders" ContentTemplate="{DataTemplate pages:OrdersPage}" />
</FlyoutItem>
</Shell>
// 详情页做路由注册(不作为一级导航)
Routing.RegisterRoute("orders/detail", typeof(OrderDetailPage));
// 跳转
await Shell.Current.GoToAsync($"orders/detail?orderId={id}");
Shell 的深链能力对推送通知和外部唤起特别好使。一条 myapp://orders/detail?orderId=42 就能直接把用户带到详情页并保留返回栈。我曾把这套用在推送落地页上,结合 .NET MAUI 推送通知完全指南里的 FCM 集成,实现从通知到目标页的一键跳转。
Xamarin.Essentials 已并入 Microsoft.Maui.Essentials,命名空间从 Xamarin.Essentials 改为 Microsoft.Maui.ApplicationModel、Microsoft.Maui.Devices、Microsoft.Maui.Media 等更细分的命名空间。Upgrade Assistant 会自动改包引用,但 API 用法有若干细微变化:
| 能力 | Xamarin.Essentials | .NET MAUI 等价 API | 迁移注意 |
| 获取设备信息 | DeviceInfo.Platform | DeviceInfo.Current.Platform | 静态调用改为单例 |
| 连接性检测 | Connectivity.NetworkAccess | Connectivity.Current.NetworkAccess | 同上 |
| 地理位置 | Geolocation.GetLocationAsync | Geolocation.Default.GetLocationAsync | 使用 Default 单例注入 |
| 安全存储 | SecureStorage.SetAsync | SecureStorage.Default.SetAsync | iOS 需在 entitlements 声明 Keychain |
| 浏览器打开 | Browser.OpenAsync | Browser.Default.OpenAsync | Android 需 Intent Query 声明 |
| 文件选择 | FilePicker.PickAsync | FilePicker.Default.PickAsync | 返回类型微调 |
关键区别在于 Default 单例注入:MAUI 里所有 Essentials API 都提供 ISomething Default 静态属性,也可以通过 DI 注入 IGeolocation、IPreferences 接口。生产项目里建议走接口注入,测试时可以 mock。
.NET MAUI Community Toolkit 完全取代了 Xamarin.CommunityToolkit,但不是简单的名称替换:Popup、Snackbar、Toast、Behavior 等大部分控件的 API 都做了重写,尤其是 Popup 从原来的 Popup 基类改为 Microsoft.Maui.Controls.Popup,事件模型也变了。建议逐一走一遍 MAUI Community Toolkit 文档比对。
迁移后验证与常见坑
Upgrade Assistant 跑完、Renderer 全部重写、Shell 上线之后,别急着发版。迁移后有五类问题是我在三次生产迁移里稳定复现的:
- iOS ObjC 桥接失败:老的
Foundation.Export 特性在部分场景不再自动生成绑定。UIViewController 生命周期方法覆盖需要显式加 [Export]。构建能过但运行时崩,日志里出现 Foundation.MonoTouchException。
- StackLayout 布局漂移:MAUI 的
StackLayout 重写了测量逻辑,同样的子控件在 Xamarin.Forms 里居中,在 MAUI 里可能贴左。建议全量换成 VerticalStackLayout 或 Grid,性能也更好。
- 字体加载缺失:Xamarin.Forms 时代放在平台项目里的字体文件,迁移后需要挪到
Resources/Fonts 并在 MauiProgram.cs 用 ConfigureFonts 显式注册,否则运行时找不到。
- 图像资源尺寸问题:MAUI 用 SVG 或单一 PNG,通过
MauiImage 自动生成多分辨率变体。旧项目里 drawable-hdpi、Resources.xcassets 分散的图片需要合并到 Resources/Images。
- Handler 注册顺序:
ConfigureMauiHandlers 必须在 UseMauiApp 之后调用,否则默认 Handler 会覆盖你的自定义 Handler。
验证阶段务必跑一遍完整的性能基线测试。迁移到 MAUI 10 后,配合我在 .NET MAUI 10 性能优化完全指南里给的那套 AOT 配置,冷启动应该比原 Xamarin.Forms 版本快 30% 以上;如果慢了,多半是 XAML 编译没开、AOT 没启用,或者 Renderer 兼容层还没清理干净。
常见问题
从 Xamarin.Forms 迁移到 .NET MAUI 需要多长时间?
典型中等复杂度应用(30–60 页面,10–20 个 Custom Renderer,一个组件库依赖)在 AI 辅助下需要 4–8 周,大型企业应用(100+ 页面、深度平台代码)通常需要 3–6 个月。少于 20 页面且没有 Custom Renderer 的简单应用可以在 1–2 周内完成。
.NET Upgrade Assistant 能自动完成全部迁移吗?
不能。Upgrade Assistant 自动处理项目文件转换、NuGet 包替换和命名空间重写,能覆盖约 40% 的机械性工作。但 Custom Renderer 转 Handler、DependencyService 转 DI、导航重构、Effect 与 Behavior 迁移仍需人工完成。
迁移后原来的 Custom Renderer 还能继续用吗?
通过 Microsoft.Maui.Controls.Compatibility 兼容层可以在过渡期继续用旧 Renderer,但该包会在未来 MAUI 版本里被移除。建议迁移窗口内一次性把所有 Renderer 重写为 Handler 或 Mapper,避免二次重构成本。
大项目应该手动迁移还是用 Upgrade Assistant?
无论项目多大都应该先跑 Upgrade Assistant,它把机械性工作自动化,不会破坏代码逻辑。手动迁移只适合项目非常小或有特殊工程约束(如全定制构建管线)的场景,纯手动会浪费 30% 以上的工作量。
Visual Studio 2026 还能加载 Xamarin.Forms 项目吗?
不能。Visual Studio 2026 默认不再加载 Xamarin.Android、Xamarin.iOS 项目类型,项目会显示为"不兼容"。Visual Studio 2022 v17.11 是最后一个官方支持 Xamarin 的版本,但也无法兼容 Xcode 16.4+ 与最新 Android Gradle Plugin。
迁移过程中如何处理没有 MAUI 版本的第三方 NuGet 包?
三个策略:优先在 NuGet.org 搜索同功能替代品(大部分热门包已有 MAUI 版本);其次评估该功能是否可以用 MAUI 内置 API 或 CommunityToolkit 替代;实在没有替代品的,可以把旧包源代码 fork 到内部仓库,手动适配到 .NET 10 目标框架。