From ff2381695ac9557dd6758756842dc6e0ad60abd8 Mon Sep 17 00:00:00 2001 From: Jonathan Peppers Date: Thu, 16 Apr 2026 10:06:16 -0500 Subject: [PATCH 1/5] Port TypeMapObjectsXmlFile.Import to XmlReader streaming Replace XDocument.Load (full DOM parse per assembly) with forward-only XmlReader streaming in the Import path. This avoids allocating the entire XML DOM tree for each .typemap.xml file. - Replace Import, ImportDebugData, ImportReleaseData methods to use XmlReader - Add ReadDebugEntries, ReadReleaseEntries, ReadReleaseScratchEntries, ReadReleaseEntry helper methods for streaming reads - Use depth-tracking to robustly detect container end-elements - Remove unused System.Linq, System.Xml.Linq, NuGet.Packaging usings - Export side (XmlWriter) is unchanged Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .../Utilities/TypeMapObjectsXmlFile.cs | 183 +++++++++++------- 1 file changed, 112 insertions(+), 71 deletions(-) diff --git a/src/Xamarin.Android.Build.Tasks/Utilities/TypeMapObjectsXmlFile.cs b/src/Xamarin.Android.Build.Tasks/Utilities/TypeMapObjectsXmlFile.cs index 5be21da534a..0cfdcf3b233 100644 --- a/src/Xamarin.Android.Build.Tasks/Utilities/TypeMapObjectsXmlFile.cs +++ b/src/Xamarin.Android.Build.Tasks/Utilities/TypeMapObjectsXmlFile.cs @@ -2,12 +2,9 @@ using System.Collections.Generic; using System.Globalization; using System.IO; -using System.Linq; using System.Xml; -using System.Xml.Linq; using Microsoft.Android.Build.Tasks; using Microsoft.Build.Utilities; -using NuGet.Packaging; using ModuleReleaseData = Xamarin.Android.Tasks.TypeMapGenerator.ModuleReleaseData; using TypeMapDebugEntry = Xamarin.Android.Tasks.TypeMapGenerator.TypeMapDebugEntry; @@ -175,13 +172,15 @@ public static TypeMapObjectsXmlFile Import (string filename) if (fi.Length == 0) return unscanned; - var xml = XDocument.Load (filename); - var root = xml.Root ?? throw new InvalidOperationException ($"Invalid XML file '{filename}'"); + using var reader = XmlReader.Create (filename); - var type = root.GetRequiredAttribute ("type"); - var assemblyName = root.GetAttributeOrDefault ("assembly-name", (string?)null); - var mvid = Guid.Parse (root.GetAttributeOrDefault ("mvid", Guid.Empty.ToString ())); - var foundJniNativeRegistration = root.GetAttributeOrDefault ("found-jni-native-registration", false); + if (!reader.ReadToFollowing ("api")) + throw new InvalidOperationException ($"Invalid XML file '{filename}'"); + + var type = reader.GetAttribute ("type") ?? throw new InvalidOperationException ("Missing required attribute 'type'"); + var assemblyName = reader.GetAttribute ("assembly-name"); + var mvid = Guid.TryParse (reader.GetAttribute ("mvid"), out var parsedMvid) ? parsedMvid : Guid.Empty; + var foundJniNativeRegistration = bool.TryParse (reader.GetAttribute ("found-jni-native-registration"), out var parsedJni) && parsedJni; var file = new TypeMapObjectsXmlFile { WasScanned = true, @@ -191,100 +190,142 @@ public static TypeMapObjectsXmlFile Import (string filename) }; if (type == "debug") - ImportDebugData (root, file); + ImportDebugData (reader, file); else if (type == "release") - ImportReleaseData (root, file); + ImportReleaseData (reader, file); return file; } - static void ImportDebugData (XElement root, TypeMapObjectsXmlFile file) + static void ImportDebugData (XmlReader reader, TypeMapObjectsXmlFile file) { - var assemblyName = root.GetAttributeOrDefault ("assembly-name", string.Empty); + var assemblyName = file.AssemblyName ?? string.Empty; var isMonoAndroid = assemblyName == "Mono.Android"; - var javaToManaged = root.Element ("java-to-managed"); - if (javaToManaged is not null) { - foreach (var entry in javaToManaged.Elements ("entry")) - file.JavaToManagedDebugEntries.Add (FromDebugEntryXml (entry, assemblyName, isMonoAndroid)); + while (reader.Read ()) { + if (reader.NodeType != XmlNodeType.Element) + continue; + + if (reader.Name == "java-to-managed") + ReadDebugEntries (reader, file.JavaToManagedDebugEntries, assemblyName, isMonoAndroid); + else if (reader.Name == "managed-to-java") + ReadDebugEntries (reader, file.ManagedToJavaDebugEntries, assemblyName, isMonoAndroid); } + } - var managedToJava = root.Element ("managed-to-java"); + static void ReadDebugEntries (XmlReader reader, List entries, string assemblyName, bool isMonoAndroid) + { + if (reader.IsEmptyElement) + return; - if (managedToJava is not null) { - foreach (var entry in managedToJava.Elements ("entry")) - file.ManagedToJavaDebugEntries.Add (FromDebugEntryXml (entry, assemblyName, isMonoAndroid)); + int depth = reader.Depth; + + while (reader.Read ()) { + if (reader.NodeType == XmlNodeType.EndElement && reader.Depth == depth) + return; + + if (reader.NodeType == XmlNodeType.Element && reader.Name == "entry") { + entries.Add (new TypeMapDebugEntry { + JavaName = reader.GetAttribute ("java-name") ?? string.Empty, + ManagedName = reader.GetAttribute ("managed-name") ?? string.Empty, + ManagedTypeTokenId = uint.TryParse (reader.GetAttribute ("managed-type-token-id"), out var tokenId) ? tokenId : 0u, + SkipInJavaToManaged = bool.TryParse (reader.GetAttribute ("skip-in-java-to-managed"), out var skip) && skip, + IsInvoker = bool.TryParse (reader.GetAttribute ("is-invoker"), out var invoker) && invoker, + IsMonoAndroid = isMonoAndroid, + AssemblyName = assemblyName, + }); + } } } - static void ImportReleaseData (XElement root, TypeMapObjectsXmlFile file) + static void ImportReleaseData (XmlReader reader, TypeMapObjectsXmlFile file) { - var module = root.Element ("module"); - - if (module is null) + if (!reader.ReadToFollowing ("module")) return; file.ModuleReleaseData = new ModuleReleaseData { - AssemblyName = module.GetAttributeOrDefault ("assembly-name", string.Empty), - Mvid = Guid.Parse (module.GetAttributeOrDefault ("mvid", Guid.Empty.ToString ())), - MvidBytes = Convert.FromBase64String (module.GetAttributeOrDefault ("mvid-bytes", string.Empty)), + AssemblyName = reader.GetAttribute ("assembly-name") ?? string.Empty, + Mvid = Guid.TryParse (reader.GetAttribute ("mvid"), out var mvid) ? mvid : Guid.Empty, + MvidBytes = Convert.FromBase64String (reader.GetAttribute ("mvid-bytes") ?? string.Empty), TypesScratch = new Dictionary (StringComparer.Ordinal), DuplicateTypes = new List (), }; - if (module.Element ("types") is XElement types) - file.ModuleReleaseData.Types = types.Elements ("entry") - .Select (FromReleaseEntryXml) - .ToArray (); - - if (module.Element ("duplicates") is XElement duplicates) - file.ModuleReleaseData.DuplicateTypes.AddRange (duplicates.Elements ("entry") - .Select (FromReleaseEntryXml)); + if (reader.IsEmptyElement) + return; - if (module.Element ("types-scratch") is XElement typesScratch) - file.ModuleReleaseData.TypesScratch.AddRange (typesScratch.Elements ("entry") - .Select (elem => new KeyValuePair (elem.GetAttributeOrDefault ("key", string.Empty), FromReleaseEntryXml (elem)))); + int depth = reader.Depth; + + while (reader.Read ()) { + if (reader.NodeType == XmlNodeType.EndElement && reader.Depth == depth) + return; + + if (reader.NodeType != XmlNodeType.Element) + continue; + + switch (reader.Name) { + case "types": + var types = new List (); + ReadReleaseEntries (reader, types); + file.ModuleReleaseData.Types = types.ToArray (); + break; + case "duplicates": + ReadReleaseEntries (reader, file.ModuleReleaseData.DuplicateTypes); + break; + case "types-scratch": + ReadReleaseScratchEntries (reader, file.ModuleReleaseData.TypesScratch); + break; + } + } } - public static void WriteEmptyFile (string destination, TaskLoggingHelper log) + static void ReadReleaseEntries (XmlReader reader, List entries) { - log.LogDebugMessage ($"Writing empty file '{destination}'"); + if (reader.IsEmptyElement) + return; - // We write a zero byte file to indicate the file couldn't have JLO types and wasn't scanned - File.Create (destination).Dispose (); + int depth = reader.Depth; + + while (reader.Read ()) { + if (reader.NodeType == XmlNodeType.EndElement && reader.Depth == depth) + return; + + if (reader.NodeType == XmlNodeType.Element && reader.Name == "entry") + entries.Add (ReadReleaseEntry (reader)); + } } - static TypeMapDebugEntry FromDebugEntryXml (XElement entry, string assemblyName, bool isMonoAndroid) + static void ReadReleaseScratchEntries (XmlReader reader, Dictionary entries) { - var javaName = entry.GetAttributeOrDefault ("java-name", string.Empty); - var managedName = entry.GetAttributeOrDefault ("managed-name", string.Empty); - var skipInJavaToManaged = entry.GetAttributeOrDefault ("skip-in-java-to-managed", false); - var isInvoker = entry.GetAttributeOrDefault ("is-invoker", false); - var managedTokenId = entry.GetAttributeOrDefault ("managed-type-token-id", (uint)0); - - return new TypeMapDebugEntry { - JavaName = javaName, - ManagedName = managedName, - ManagedTypeTokenId = managedTokenId, - SkipInJavaToManaged = skipInJavaToManaged, - IsInvoker = isInvoker, - IsMonoAndroid = isMonoAndroid, - AssemblyName = assemblyName, - }; + if (reader.IsEmptyElement) + return; + + int depth = reader.Depth; + + while (reader.Read ()) { + if (reader.NodeType == XmlNodeType.EndElement && reader.Depth == depth) + return; + + if (reader.NodeType == XmlNodeType.Element && reader.Name == "entry") { + var key = reader.GetAttribute ("key") ?? string.Empty; + entries[key] = ReadReleaseEntry (reader); + } + } } - static TypeMapReleaseEntry FromReleaseEntryXml (XElement entry) + static TypeMapReleaseEntry ReadReleaseEntry (XmlReader reader) => new TypeMapReleaseEntry { + JavaName = reader.GetAttribute ("java-name") ?? string.Empty, + ManagedTypeName = reader.GetAttribute ("managed-type-name") ?? string.Empty, + Token = uint.TryParse (reader.GetAttribute ("token"), out var token) ? token : 0u, + SkipInJavaToManaged = bool.TryParse (reader.GetAttribute ("skip-in-java-to-managed"), out var skip) && skip, + }; + + public static void WriteEmptyFile (string destination, TaskLoggingHelper log) { - var javaName = entry.GetAttributeOrDefault ("java-name", string.Empty); - var managedTypeName = entry.GetAttributeOrDefault ("managed-type-name", string.Empty); - var token = entry.GetAttributeOrDefault ("token", 0u); - var skipInJavaToManaged = entry.GetAttributeOrDefault ("skip-in-java-to-managed", false); - - return new TypeMapReleaseEntry { - JavaName = javaName, - ManagedTypeName = managedTypeName, - Token = token, - SkipInJavaToManaged = skipInJavaToManaged, - }; + log.LogDebugMessage ($"Writing empty file '{destination}'"); + + // We write a zero byte file to indicate the file couldn't have JLO types and wasn't scanned + File.Create (destination).Dispose (); } + } From da7fc283799c8f15741a4659d8c513fefc9cb096 Mon Sep 17 00:00:00 2001 From: Jonathan Peppers Date: Thu, 16 Apr 2026 10:16:06 -0500 Subject: [PATCH 2/5] fixup: reorder methods to minimize diff noise Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .../Utilities/TypeMapObjectsXmlFile.cs | 92 ++++++++++--------- 1 file changed, 49 insertions(+), 43 deletions(-) diff --git a/src/Xamarin.Android.Build.Tasks/Utilities/TypeMapObjectsXmlFile.cs b/src/Xamarin.Android.Build.Tasks/Utilities/TypeMapObjectsXmlFile.cs index 0cfdcf3b233..baa579b4cde 100644 --- a/src/Xamarin.Android.Build.Tasks/Utilities/TypeMapObjectsXmlFile.cs +++ b/src/Xamarin.Android.Build.Tasks/Utilities/TypeMapObjectsXmlFile.cs @@ -213,31 +213,6 @@ static void ImportDebugData (XmlReader reader, TypeMapObjectsXmlFile file) } } - static void ReadDebugEntries (XmlReader reader, List entries, string assemblyName, bool isMonoAndroid) - { - if (reader.IsEmptyElement) - return; - - int depth = reader.Depth; - - while (reader.Read ()) { - if (reader.NodeType == XmlNodeType.EndElement && reader.Depth == depth) - return; - - if (reader.NodeType == XmlNodeType.Element && reader.Name == "entry") { - entries.Add (new TypeMapDebugEntry { - JavaName = reader.GetAttribute ("java-name") ?? string.Empty, - ManagedName = reader.GetAttribute ("managed-name") ?? string.Empty, - ManagedTypeTokenId = uint.TryParse (reader.GetAttribute ("managed-type-token-id"), out var tokenId) ? tokenId : 0u, - SkipInJavaToManaged = bool.TryParse (reader.GetAttribute ("skip-in-java-to-managed"), out var skip) && skip, - IsInvoker = bool.TryParse (reader.GetAttribute ("is-invoker"), out var invoker) && invoker, - IsMonoAndroid = isMonoAndroid, - AssemblyName = assemblyName, - }); - } - } - } - static void ImportReleaseData (XmlReader reader, TypeMapObjectsXmlFile file) { if (!reader.ReadToFollowing ("module")) @@ -279,6 +254,53 @@ static void ImportReleaseData (XmlReader reader, TypeMapObjectsXmlFile file) } } + public static void WriteEmptyFile (string destination, TaskLoggingHelper log) + { + log.LogDebugMessage ($"Writing empty file '{destination}'"); + + // We write a zero byte file to indicate the file couldn't have JLO types and wasn't scanned + File.Create (destination).Dispose (); + } + + static TypeMapDebugEntry FromDebugEntryXml (XmlReader reader, string assemblyName, bool isMonoAndroid) + { + return new TypeMapDebugEntry { + JavaName = reader.GetAttribute ("java-name") ?? string.Empty, + ManagedName = reader.GetAttribute ("managed-name") ?? string.Empty, + ManagedTypeTokenId = uint.TryParse (reader.GetAttribute ("managed-type-token-id"), out var tokenId) ? tokenId : 0u, + SkipInJavaToManaged = bool.TryParse (reader.GetAttribute ("skip-in-java-to-managed"), out var skip) && skip, + IsInvoker = bool.TryParse (reader.GetAttribute ("is-invoker"), out var invoker) && invoker, + IsMonoAndroid = isMonoAndroid, + AssemblyName = assemblyName, + }; + } + + static TypeMapReleaseEntry FromReleaseEntryXml (XmlReader reader) + { + return new TypeMapReleaseEntry { + JavaName = reader.GetAttribute ("java-name") ?? string.Empty, + ManagedTypeName = reader.GetAttribute ("managed-type-name") ?? string.Empty, + Token = uint.TryParse (reader.GetAttribute ("token"), out var token) ? token : 0u, + SkipInJavaToManaged = bool.TryParse (reader.GetAttribute ("skip-in-java-to-managed"), out var skip) && skip, + }; + } + + static void ReadDebugEntries (XmlReader reader, List entries, string assemblyName, bool isMonoAndroid) + { + if (reader.IsEmptyElement) + return; + + int depth = reader.Depth; + + while (reader.Read ()) { + if (reader.NodeType == XmlNodeType.EndElement && reader.Depth == depth) + return; + + if (reader.NodeType == XmlNodeType.Element && reader.Name == "entry") + entries.Add (FromDebugEntryXml (reader, assemblyName, isMonoAndroid)); + } + } + static void ReadReleaseEntries (XmlReader reader, List entries) { if (reader.IsEmptyElement) @@ -291,7 +313,7 @@ static void ReadReleaseEntries (XmlReader reader, List entr return; if (reader.NodeType == XmlNodeType.Element && reader.Name == "entry") - entries.Add (ReadReleaseEntry (reader)); + entries.Add (FromReleaseEntryXml (reader)); } } @@ -308,24 +330,8 @@ static void ReadReleaseScratchEntries (XmlReader reader, Dictionary new TypeMapReleaseEntry { - JavaName = reader.GetAttribute ("java-name") ?? string.Empty, - ManagedTypeName = reader.GetAttribute ("managed-type-name") ?? string.Empty, - Token = uint.TryParse (reader.GetAttribute ("token"), out var token) ? token : 0u, - SkipInJavaToManaged = bool.TryParse (reader.GetAttribute ("skip-in-java-to-managed"), out var skip) && skip, - }; - - public static void WriteEmptyFile (string destination, TaskLoggingHelper log) - { - log.LogDebugMessage ($"Writing empty file '{destination}'"); - - // We write a zero byte file to indicate the file couldn't have JLO types and wasn't scanned - File.Create (destination).Dispose (); - } - } From c5534f137ddf459fb074f6870fdadd6191081938 Mon Sep 17 00:00:00 2001 From: Jonathan Peppers Date: Thu, 16 Apr 2026 10:22:28 -0500 Subject: [PATCH 3/5] fixup: match original error behavior, improve error messages - Use Guid.Parse / Convert.ToBoolean / Convert.ChangeType to throw on invalid values (matching original GetAttributeOrDefault behavior) - Add GetAttributeOrDefault helper for XmlReader - Include filename in error messages - Check IsNullOrWhiteSpace for 'type' attribute (matching GetRequiredAttribute) Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .../Utilities/TypeMapObjectsXmlFile.cs | 35 ++++++++++++++----- 1 file changed, 26 insertions(+), 9 deletions(-) diff --git a/src/Xamarin.Android.Build.Tasks/Utilities/TypeMapObjectsXmlFile.cs b/src/Xamarin.Android.Build.Tasks/Utilities/TypeMapObjectsXmlFile.cs index baa579b4cde..5473b37d45d 100644 --- a/src/Xamarin.Android.Build.Tasks/Utilities/TypeMapObjectsXmlFile.cs +++ b/src/Xamarin.Android.Build.Tasks/Utilities/TypeMapObjectsXmlFile.cs @@ -177,10 +177,16 @@ public static TypeMapObjectsXmlFile Import (string filename) if (!reader.ReadToFollowing ("api")) throw new InvalidOperationException ($"Invalid XML file '{filename}'"); - var type = reader.GetAttribute ("type") ?? throw new InvalidOperationException ("Missing required attribute 'type'"); + var type = reader.GetAttribute ("type"); + + if (string.IsNullOrWhiteSpace (type)) + throw new InvalidOperationException ($"Missing required attribute 'type' in '{filename}'"); + var assemblyName = reader.GetAttribute ("assembly-name"); - var mvid = Guid.TryParse (reader.GetAttribute ("mvid"), out var parsedMvid) ? parsedMvid : Guid.Empty; - var foundJniNativeRegistration = bool.TryParse (reader.GetAttribute ("found-jni-native-registration"), out var parsedJni) && parsedJni; + var mvidValue = reader.GetAttribute ("mvid"); + var mvid = string.IsNullOrWhiteSpace (mvidValue) ? Guid.Empty : Guid.Parse (mvidValue); + var foundJniValue = reader.GetAttribute ("found-jni-native-registration"); + var foundJniNativeRegistration = !string.IsNullOrWhiteSpace (foundJniValue) && Convert.ToBoolean (foundJniValue); var file = new TypeMapObjectsXmlFile { WasScanned = true, @@ -218,9 +224,10 @@ static void ImportReleaseData (XmlReader reader, TypeMapObjectsXmlFile file) if (!reader.ReadToFollowing ("module")) return; + var mvidValue = reader.GetAttribute ("mvid"); file.ModuleReleaseData = new ModuleReleaseData { AssemblyName = reader.GetAttribute ("assembly-name") ?? string.Empty, - Mvid = Guid.TryParse (reader.GetAttribute ("mvid"), out var mvid) ? mvid : Guid.Empty, + Mvid = string.IsNullOrWhiteSpace (mvidValue) ? Guid.Empty : Guid.Parse (mvidValue), MvidBytes = Convert.FromBase64String (reader.GetAttribute ("mvid-bytes") ?? string.Empty), TypesScratch = new Dictionary (StringComparer.Ordinal), DuplicateTypes = new List (), @@ -267,9 +274,9 @@ static TypeMapDebugEntry FromDebugEntryXml (XmlReader reader, string assemblyNam return new TypeMapDebugEntry { JavaName = reader.GetAttribute ("java-name") ?? string.Empty, ManagedName = reader.GetAttribute ("managed-name") ?? string.Empty, - ManagedTypeTokenId = uint.TryParse (reader.GetAttribute ("managed-type-token-id"), out var tokenId) ? tokenId : 0u, - SkipInJavaToManaged = bool.TryParse (reader.GetAttribute ("skip-in-java-to-managed"), out var skip) && skip, - IsInvoker = bool.TryParse (reader.GetAttribute ("is-invoker"), out var invoker) && invoker, + ManagedTypeTokenId = GetAttributeOrDefault (reader, "managed-type-token-id", 0u), + SkipInJavaToManaged = GetAttributeOrDefault (reader, "skip-in-java-to-managed", false), + IsInvoker = GetAttributeOrDefault (reader, "is-invoker", false), IsMonoAndroid = isMonoAndroid, AssemblyName = assemblyName, }; @@ -280,11 +287,21 @@ static TypeMapReleaseEntry FromReleaseEntryXml (XmlReader reader) return new TypeMapReleaseEntry { JavaName = reader.GetAttribute ("java-name") ?? string.Empty, ManagedTypeName = reader.GetAttribute ("managed-type-name") ?? string.Empty, - Token = uint.TryParse (reader.GetAttribute ("token"), out var token) ? token : 0u, - SkipInJavaToManaged = bool.TryParse (reader.GetAttribute ("skip-in-java-to-managed"), out var skip) && skip, + Token = GetAttributeOrDefault (reader, "token", 0u), + SkipInJavaToManaged = GetAttributeOrDefault (reader, "skip-in-java-to-managed", false), }; } + static T GetAttributeOrDefault (XmlReader reader, string name, T defaultValue) + { + var value = reader.GetAttribute (name); + + if (string.IsNullOrWhiteSpace (value)) + return defaultValue; + + return (T) Convert.ChangeType (value, typeof (T)); + } + static void ReadDebugEntries (XmlReader reader, List entries, string assemblyName, bool isMonoAndroid) { if (reader.IsEmptyElement) From 679a791ae578e4befc6d39db8d14be1a95a52ea5 Mon Sep 17 00:00:00 2001 From: Jonathan Peppers Date: Thu, 16 Apr 2026 11:11:42 -0500 Subject: [PATCH 4/5] fixup: match original behavior for mvid-bytes and duplicate keys - Use GetAttributeOrDefault for mvid-bytes to handle whitespace the same way as the original GetAttributeOrDefault on XElement - Use entries.Add() instead of entries[key] to throw on duplicate keys, matching the original NuGet.Packaging AddRange behavior Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .../Utilities/TypeMapObjectsXmlFile.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Xamarin.Android.Build.Tasks/Utilities/TypeMapObjectsXmlFile.cs b/src/Xamarin.Android.Build.Tasks/Utilities/TypeMapObjectsXmlFile.cs index 5473b37d45d..e3936199191 100644 --- a/src/Xamarin.Android.Build.Tasks/Utilities/TypeMapObjectsXmlFile.cs +++ b/src/Xamarin.Android.Build.Tasks/Utilities/TypeMapObjectsXmlFile.cs @@ -228,7 +228,7 @@ static void ImportReleaseData (XmlReader reader, TypeMapObjectsXmlFile file) file.ModuleReleaseData = new ModuleReleaseData { AssemblyName = reader.GetAttribute ("assembly-name") ?? string.Empty, Mvid = string.IsNullOrWhiteSpace (mvidValue) ? Guid.Empty : Guid.Parse (mvidValue), - MvidBytes = Convert.FromBase64String (reader.GetAttribute ("mvid-bytes") ?? string.Empty), + MvidBytes = Convert.FromBase64String (GetAttributeOrDefault (reader, "mvid-bytes", string.Empty)), TypesScratch = new Dictionary (StringComparer.Ordinal), DuplicateTypes = new List (), }; @@ -347,7 +347,7 @@ static void ReadReleaseScratchEntries (XmlReader reader, Dictionary Date: Thu, 16 Apr 2026 14:23:45 -0500 Subject: [PATCH 5/5] fixup: use extension methods, GetAttributeOrDefault, InvariantCulture - Use .IsNullOrWhiteSpace() extension methods for NRT flow analysis - Reuse GetAttributeOrDefault for found-jni-native-registration - Pass CultureInfo.InvariantCulture to Convert.ChangeType for round-trip safety with Export side Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .../Utilities/TypeMapObjectsXmlFile.cs | 13 ++++++------- 1 file changed, 6 insertions(+), 7 deletions(-) diff --git a/src/Xamarin.Android.Build.Tasks/Utilities/TypeMapObjectsXmlFile.cs b/src/Xamarin.Android.Build.Tasks/Utilities/TypeMapObjectsXmlFile.cs index e3936199191..1a15f000f0c 100644 --- a/src/Xamarin.Android.Build.Tasks/Utilities/TypeMapObjectsXmlFile.cs +++ b/src/Xamarin.Android.Build.Tasks/Utilities/TypeMapObjectsXmlFile.cs @@ -179,14 +179,13 @@ public static TypeMapObjectsXmlFile Import (string filename) var type = reader.GetAttribute ("type"); - if (string.IsNullOrWhiteSpace (type)) + if (type.IsNullOrWhiteSpace ()) throw new InvalidOperationException ($"Missing required attribute 'type' in '{filename}'"); var assemblyName = reader.GetAttribute ("assembly-name"); var mvidValue = reader.GetAttribute ("mvid"); - var mvid = string.IsNullOrWhiteSpace (mvidValue) ? Guid.Empty : Guid.Parse (mvidValue); - var foundJniValue = reader.GetAttribute ("found-jni-native-registration"); - var foundJniNativeRegistration = !string.IsNullOrWhiteSpace (foundJniValue) && Convert.ToBoolean (foundJniValue); + var mvid = mvidValue.IsNullOrWhiteSpace () ? Guid.Empty : Guid.Parse (mvidValue); + var foundJniNativeRegistration = GetAttributeOrDefault (reader, "found-jni-native-registration", false); var file = new TypeMapObjectsXmlFile { WasScanned = true, @@ -227,7 +226,7 @@ static void ImportReleaseData (XmlReader reader, TypeMapObjectsXmlFile file) var mvidValue = reader.GetAttribute ("mvid"); file.ModuleReleaseData = new ModuleReleaseData { AssemblyName = reader.GetAttribute ("assembly-name") ?? string.Empty, - Mvid = string.IsNullOrWhiteSpace (mvidValue) ? Guid.Empty : Guid.Parse (mvidValue), + Mvid = mvidValue.IsNullOrWhiteSpace () ? Guid.Empty : Guid.Parse (mvidValue), MvidBytes = Convert.FromBase64String (GetAttributeOrDefault (reader, "mvid-bytes", string.Empty)), TypesScratch = new Dictionary (StringComparer.Ordinal), DuplicateTypes = new List (), @@ -296,10 +295,10 @@ static T GetAttributeOrDefault (XmlReader reader, string name, T defaultValue { var value = reader.GetAttribute (name); - if (string.IsNullOrWhiteSpace (value)) + if (value.IsNullOrWhiteSpace ()) return defaultValue; - return (T) Convert.ChangeType (value, typeof (T)); + return (T) Convert.ChangeType (value, typeof (T), CultureInfo.InvariantCulture); } static void ReadDebugEntries (XmlReader reader, List entries, string assemblyName, bool isMonoAndroid)