From 3f1ca2482dbbf117c04b7df12d23bb29bc7faa52 Mon Sep 17 00:00:00 2001 From: h3xds1nz Date: Sat, 14 Sep 2024 18:33:37 +0200 Subject: [PATCH 01/17] Rename _registeredResourceManagers -> s_registeredResourceManagers --- .../MS/Internal/AppModel/ResourceContainer.cs | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/Microsoft.DotNet.Wpf/src/PresentationFramework/MS/Internal/AppModel/ResourceContainer.cs b/src/Microsoft.DotNet.Wpf/src/PresentationFramework/MS/Internal/AppModel/ResourceContainer.cs index 244de190f12..9a63c0bbaff 100644 --- a/src/Microsoft.DotNet.Wpf/src/PresentationFramework/MS/Internal/AppModel/ResourceContainer.cs +++ b/src/Microsoft.DotNet.Wpf/src/PresentationFramework/MS/Internal/AppModel/ResourceContainer.cs @@ -294,13 +294,13 @@ private void OnAssemblyLoadEventHandler(object sender, AssemblyLoadEventArgs arg private void UpdateCachedRMW(string key, Assembly assembly) { - if (_registeredResourceManagers.ContainsKey(key)) + if (s_registeredResourceManagers.ContainsKey(key)) { // Update the ResourceManagerWrapper with the new assembly. // Note Package caches Part and Part holds on to ResourceManagerWrapper. Package does not provide a way for // us to update their cache, so we update the assembly that the ResourceManagerWrapper holds on to. This way the // Part cached in the Package class can reference the new dll too. - _registeredResourceManagers[key].Assembly = assembly; + s_registeredResourceManagers[key].Assembly = assembly; } } @@ -328,7 +328,7 @@ private ResourceManagerWrapper GetResourceManagerWrapper(Uri uri, out string par { string key = assemblyName + assemblyVersion + assemblyKey; - _registeredResourceManagers.TryGetValue(key.ToLowerInvariant(), out rmwResult); + s_registeredResourceManagers.TryGetValue(key.ToLowerInvariant(), out rmwResult); // first time. Add this to the hash table if (rmwResult == null) @@ -348,7 +348,7 @@ private ResourceManagerWrapper GetResourceManagerWrapper(Uri uri, out string par rmwResult = new ResourceManagerWrapper(assembly); } - _registeredResourceManagers[key.ToLowerInvariant()] = rmwResult; + s_registeredResourceManagers[key.ToLowerInvariant()] = rmwResult; } } @@ -384,7 +384,7 @@ private ResourceManagerWrapper GetResourceManagerWrapper(Uri uri, out string par #region Private Members - private static Dictionary _registeredResourceManagers = new Dictionary(); + private static Dictionary s_registeredResourceManagers = new(); private static ResourceManagerWrapper _applicationResourceManagerWrapper = null; private static FileShare _fileShare = FileShare.Read; private static bool assemblyLoadhandlerAttached = false; From 2ec875174e9747c3526ae58a432bc3638d9efb9c Mon Sep 17 00:00:00 2001 From: h3xds1nz Date: Sat, 14 Sep 2024 18:37:31 +0200 Subject: [PATCH 02/17] Use StringComparer.OrdinalIgnoreCase instead of name lowering --- .../MS/Internal/AppModel/ResourceContainer.cs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/Microsoft.DotNet.Wpf/src/PresentationFramework/MS/Internal/AppModel/ResourceContainer.cs b/src/Microsoft.DotNet.Wpf/src/PresentationFramework/MS/Internal/AppModel/ResourceContainer.cs index 9a63c0bbaff..4f409ccd371 100644 --- a/src/Microsoft.DotNet.Wpf/src/PresentationFramework/MS/Internal/AppModel/ResourceContainer.cs +++ b/src/Microsoft.DotNet.Wpf/src/PresentationFramework/MS/Internal/AppModel/ResourceContainer.cs @@ -249,7 +249,7 @@ private void OnAssemblyLoadEventHandler(object sender, AssemblyLoadEventArgs arg { AssemblyName assemblyInfo = new AssemblyName(assembly.FullName); - string assemblyName = assemblyInfo.Name.ToLowerInvariant(); + string assemblyName = assemblyInfo.Name; string assemblyKey = string.Empty; string key = assemblyName; @@ -328,7 +328,7 @@ private ResourceManagerWrapper GetResourceManagerWrapper(Uri uri, out string par { string key = assemblyName + assemblyVersion + assemblyKey; - s_registeredResourceManagers.TryGetValue(key.ToLowerInvariant(), out rmwResult); + s_registeredResourceManagers.TryGetValue(key, out rmwResult); // first time. Add this to the hash table if (rmwResult == null) @@ -348,7 +348,7 @@ private ResourceManagerWrapper GetResourceManagerWrapper(Uri uri, out string par rmwResult = new ResourceManagerWrapper(assembly); } - s_registeredResourceManagers[key.ToLowerInvariant()] = rmwResult; + s_registeredResourceManagers[key] = rmwResult; } } @@ -384,7 +384,7 @@ private ResourceManagerWrapper GetResourceManagerWrapper(Uri uri, out string par #region Private Members - private static Dictionary s_registeredResourceManagers = new(); + private static Dictionary s_registeredResourceManagers = new(StringComparer.OrdinalIgnoreCase); private static ResourceManagerWrapper _applicationResourceManagerWrapper = null; private static FileShare _fileShare = FileShare.Read; private static bool assemblyLoadhandlerAttached = false; From 5b407b2833122bdd9c6cfa454495718bf346dac3 Mon Sep 17 00:00:00 2001 From: h3xds1nz Date: Sat, 14 Sep 2024 19:06:51 +0200 Subject: [PATCH 03/17] Avoid double dictionary lookup in UpdateCachedRMW --- .../MS/Internal/AppModel/ResourceContainer.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Microsoft.DotNet.Wpf/src/PresentationFramework/MS/Internal/AppModel/ResourceContainer.cs b/src/Microsoft.DotNet.Wpf/src/PresentationFramework/MS/Internal/AppModel/ResourceContainer.cs index 4f409ccd371..1b58450edce 100644 --- a/src/Microsoft.DotNet.Wpf/src/PresentationFramework/MS/Internal/AppModel/ResourceContainer.cs +++ b/src/Microsoft.DotNet.Wpf/src/PresentationFramework/MS/Internal/AppModel/ResourceContainer.cs @@ -294,13 +294,13 @@ private void OnAssemblyLoadEventHandler(object sender, AssemblyLoadEventArgs arg private void UpdateCachedRMW(string key, Assembly assembly) { - if (s_registeredResourceManagers.ContainsKey(key)) + if (s_registeredResourceManagers.TryGetValue(key, out ResourceManagerWrapper value)) { // Update the ResourceManagerWrapper with the new assembly. // Note Package caches Part and Part holds on to ResourceManagerWrapper. Package does not provide a way for // us to update their cache, so we update the assembly that the ResourceManagerWrapper holds on to. This way the // Part cached in the Package class can reference the new dll too. - s_registeredResourceManagers[key].Assembly = assembly; + value.Assembly = assembly; } } From 2d9fa3b952e123dd6c861b3a321dbaf2f6361908 Mon Sep 17 00:00:00 2001 From: h3xds1nz Date: Sat, 14 Sep 2024 19:21:07 +0200 Subject: [PATCH 04/17] Use proper lookup in GetResourceManagerWrapper (value can never be null) --- .../MS/Internal/AppModel/ResourceContainer.cs | 9 ++------- 1 file changed, 2 insertions(+), 7 deletions(-) diff --git a/src/Microsoft.DotNet.Wpf/src/PresentationFramework/MS/Internal/AppModel/ResourceContainer.cs b/src/Microsoft.DotNet.Wpf/src/PresentationFramework/MS/Internal/AppModel/ResourceContainer.cs index 1b58450edce..eb2696b7ce1 100644 --- a/src/Microsoft.DotNet.Wpf/src/PresentationFramework/MS/Internal/AppModel/ResourceContainer.cs +++ b/src/Microsoft.DotNet.Wpf/src/PresentationFramework/MS/Internal/AppModel/ResourceContainer.cs @@ -328,15 +328,10 @@ private ResourceManagerWrapper GetResourceManagerWrapper(Uri uri, out string par { string key = assemblyName + assemblyVersion + assemblyKey; - s_registeredResourceManagers.TryGetValue(key, out rmwResult); - // first time. Add this to the hash table - if (rmwResult == null) + if (!s_registeredResourceManagers.TryGetValue(key, out rmwResult)) { - Assembly assembly; - - assembly = BaseUriHelper.GetLoadedAssembly(assemblyName, assemblyVersion, assemblyKey); - + Assembly assembly = BaseUriHelper.GetLoadedAssembly(assemblyName, assemblyVersion, assemblyKey); if (assembly.Equals(Application.ResourceAssembly)) { // This Uri maps to Application Entry assembly even though it has ";component". From 4d3bf759033d4dc7f92200bb4832e210dd3847c1 Mon Sep 17 00:00:00 2001 From: h3xds1nz Date: Sat, 14 Sep 2024 19:23:03 +0200 Subject: [PATCH 05/17] Use proper variable names, set as readonly variables that are never set again --- .../MS/Internal/AppModel/ResourceContainer.cs | 21 ++++++++++--------- 1 file changed, 11 insertions(+), 10 deletions(-) diff --git a/src/Microsoft.DotNet.Wpf/src/PresentationFramework/MS/Internal/AppModel/ResourceContainer.cs b/src/Microsoft.DotNet.Wpf/src/PresentationFramework/MS/Internal/AppModel/ResourceContainer.cs index eb2696b7ce1..679092ee767 100644 --- a/src/Microsoft.DotNet.Wpf/src/PresentationFramework/MS/Internal/AppModel/ResourceContainer.cs +++ b/src/Microsoft.DotNet.Wpf/src/PresentationFramework/MS/Internal/AppModel/ResourceContainer.cs @@ -44,18 +44,18 @@ internal static ResourceManagerWrapper ApplicationResourceManagerWrapper { get { - if (_applicationResourceManagerWrapper == null) + if (s_applicationResourceManagerWrapper == null) { // load main excutable assembly Assembly asmApplication = Application.ResourceAssembly; if (asmApplication != null) { - _applicationResourceManagerWrapper = new ResourceManagerWrapper(asmApplication); + s_applicationResourceManagerWrapper = new ResourceManagerWrapper(asmApplication); } } - return _applicationResourceManagerWrapper; + return s_applicationResourceManagerWrapper; } } @@ -68,7 +68,7 @@ internal static FileShare FileShare { get { - return _fileShare; + return s_fileShare; } } @@ -200,10 +200,10 @@ protected override PackagePart GetPartCore(Uri uri) // old version dll when a newer one is loaded. So whenever the AssemblyLoad event is fired, we will need to update the cache // with the newly loaded assembly. This is currently only for designer so not needed for browser hosted apps. // Attach the event handler before the first time we get the ResourceManagerWrapper. - if (!assemblyLoadhandlerAttached) + if (!s_assemblyLoadhandlerAttached) { AppDomain.CurrentDomain.AssemblyLoad += new AssemblyLoadEventHandler(OnAssemblyLoadEventHandler); - assemblyLoadhandlerAttached = true; + s_assemblyLoadhandlerAttached = true; } ResourceManagerWrapper rmWrapper = GetResourceManagerWrapper(uri, out partName, out isContentFile); @@ -379,10 +379,11 @@ private ResourceManagerWrapper GetResourceManagerWrapper(Uri uri, out string par #region Private Members - private static Dictionary s_registeredResourceManagers = new(StringComparer.OrdinalIgnoreCase); - private static ResourceManagerWrapper _applicationResourceManagerWrapper = null; - private static FileShare _fileShare = FileShare.Read; - private static bool assemblyLoadhandlerAttached = false; + private static readonly Dictionary s_registeredResourceManagers = new(StringComparer.OrdinalIgnoreCase); + private static readonly FileShare s_fileShare = FileShare.Read; + + private static ResourceManagerWrapper s_applicationResourceManagerWrapper = null; + private static bool s_assemblyLoadhandlerAttached = false; #endregion Private Members From 7b0ec37d665d05bc0ea14815b74b5fd1739cd301 Mon Sep 17 00:00:00 2001 From: h3xds1nz Date: Sat, 14 Sep 2024 19:24:35 +0200 Subject: [PATCH 06/17] Mark UpdateCachedRMW and GetResourceManagerWrapper as static functions --- .../MS/Internal/AppModel/ResourceContainer.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Microsoft.DotNet.Wpf/src/PresentationFramework/MS/Internal/AppModel/ResourceContainer.cs b/src/Microsoft.DotNet.Wpf/src/PresentationFramework/MS/Internal/AppModel/ResourceContainer.cs index 679092ee767..3a9ce082d2f 100644 --- a/src/Microsoft.DotNet.Wpf/src/PresentationFramework/MS/Internal/AppModel/ResourceContainer.cs +++ b/src/Microsoft.DotNet.Wpf/src/PresentationFramework/MS/Internal/AppModel/ResourceContainer.cs @@ -292,7 +292,7 @@ private void OnAssemblyLoadEventHandler(object sender, AssemblyLoadEventArgs arg } } - private void UpdateCachedRMW(string key, Assembly assembly) + private static void UpdateCachedRMW(string key, Assembly assembly) { if (s_registeredResourceManagers.TryGetValue(key, out ResourceManagerWrapper value)) { @@ -313,7 +313,7 @@ private void UpdateCachedRMW(string key, Assembly assembly) // The name of the file in the resource manager // A flag to indicate that this path is a known loose file at compile time // - private ResourceManagerWrapper GetResourceManagerWrapper(Uri uri, out string partName, out bool isContentFile) + private static ResourceManagerWrapper GetResourceManagerWrapper(Uri uri, out string partName, out bool isContentFile) { string assemblyName; string assemblyVersion; From c7beb74c80638c42eb2a62df62f80642d38839a8 Mon Sep 17 00:00:00 2001 From: h3xds1nz Date: Sat, 14 Sep 2024 21:36:32 +0200 Subject: [PATCH 07/17] Use string.create for key creation with initial buffer --- .../MS/Internal/AppModel/ResourceContainer.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/Microsoft.DotNet.Wpf/src/PresentationFramework/MS/Internal/AppModel/ResourceContainer.cs b/src/Microsoft.DotNet.Wpf/src/PresentationFramework/MS/Internal/AppModel/ResourceContainer.cs index 3a9ce082d2f..0c31a663220 100644 --- a/src/Microsoft.DotNet.Wpf/src/PresentationFramework/MS/Internal/AppModel/ResourceContainer.cs +++ b/src/Microsoft.DotNet.Wpf/src/PresentationFramework/MS/Internal/AppModel/ResourceContainer.cs @@ -326,7 +326,8 @@ private static ResourceManagerWrapper GetResourceManagerWrapper(Uri uri, out str if (!String.IsNullOrEmpty(assemblyName)) { - string key = assemblyName + assemblyVersion + assemblyKey; + // Create the key, this will rarely get over 128 chars in my experience + string key = string.Create(null, stackalloc char [128], $"{assemblyName}{assemblyVersion}{assemblyKey}"); // first time. Add this to the hash table if (!s_registeredResourceManagers.TryGetValue(key, out rmwResult)) From 27f512d4ef5cab08e7deca244d90fccfeac66d8b Mon Sep 17 00:00:00 2001 From: h3xds1nz Date: Sun, 15 Sep 2024 11:00:06 +0200 Subject: [PATCH 08/17] Save 100ns by using faster byte2hex function --- .../MS/Internal/AppModel/ResourceContainer.cs | 15 +++++---------- 1 file changed, 5 insertions(+), 10 deletions(-) diff --git a/src/Microsoft.DotNet.Wpf/src/PresentationFramework/MS/Internal/AppModel/ResourceContainer.cs b/src/Microsoft.DotNet.Wpf/src/PresentationFramework/MS/Internal/AppModel/ResourceContainer.cs index 0c31a663220..7cae95b6a91 100644 --- a/src/Microsoft.DotNet.Wpf/src/PresentationFramework/MS/Internal/AppModel/ResourceContainer.cs +++ b/src/Microsoft.DotNet.Wpf/src/PresentationFramework/MS/Internal/AppModel/ResourceContainer.cs @@ -250,7 +250,6 @@ private void OnAssemblyLoadEventHandler(object sender, AssemblyLoadEventArgs arg AssemblyName assemblyInfo = new AssemblyName(assembly.FullName); string assemblyName = assemblyInfo.Name; - string assemblyKey = string.Empty; string key = assemblyName; // Check if this newly loaded assembly is in the cache. If so, update the cache. @@ -271,20 +270,16 @@ private void OnAssemblyLoadEventHandler(object sender, AssemblyLoadEventArgs arg UpdateCachedRMW(key, args.LoadedAssembly); } - byte[] reqKeyToken = assemblyInfo.GetPublicKeyToken(); - for (int i = 0; i < reqKeyToken.Length; i++) + byte[] publicKeyToken = assemblyInfo.GetPublicKeyToken(); + Span assemblyKey = stackalloc char[16]; + if (Convert.TryToHexStringLower(publicKeyToken, assemblyKey, out int charsWritten) && charsWritten == 16) { - assemblyKey += reqKeyToken[i].ToString("x", NumberFormatInfo.InvariantInfo); - } - - if (!String.IsNullOrEmpty(assemblyKey)) - { - key = key + assemblyKey; + key = $"{key}{assemblyKey}"; // Check Name + Version + KeyToken UpdateCachedRMW(key, args.LoadedAssembly); - key = assemblyName + assemblyKey; + key = $"{assemblyName}{assemblyKey}"; // Check Name + KeyToken UpdateCachedRMW(key, args.LoadedAssembly); From 05d37269097a7c82485b6ef2fbc925279ae30edc Mon Sep 17 00:00:00 2001 From: h3xds1nz Date: Sun, 15 Sep 2024 14:37:03 +0200 Subject: [PATCH 09/17] Save additional 200 bytes or more in allocations --- .../MS/Internal/AppModel/ResourceContainer.cs | 37 +++++++++---------- 1 file changed, 17 insertions(+), 20 deletions(-) diff --git a/src/Microsoft.DotNet.Wpf/src/PresentationFramework/MS/Internal/AppModel/ResourceContainer.cs b/src/Microsoft.DotNet.Wpf/src/PresentationFramework/MS/Internal/AppModel/ResourceContainer.cs index 7cae95b6a91..b3bb6623d75 100644 --- a/src/Microsoft.DotNet.Wpf/src/PresentationFramework/MS/Internal/AppModel/ResourceContainer.cs +++ b/src/Microsoft.DotNet.Wpf/src/PresentationFramework/MS/Internal/AppModel/ResourceContainer.cs @@ -247,42 +247,39 @@ private void OnAssemblyLoadEventHandler(object sender, AssemblyLoadEventArgs arg // We do no care about those. Only when a assembly is loaded into the execution context, we will need to update the cache. if ((!assembly.ReflectionOnly)) { - AssemblyName assemblyInfo = new AssemblyName(assembly.FullName); + AssemblyName assemblyInfo = new(assembly.FullName); string assemblyName = assemblyInfo.Name; - string key = assemblyName; + string assemblyNameVersion = null; // Check if this newly loaded assembly is in the cache. If so, update the cache. // If it is not in cache, do not do anything. It will be added on demand. - // The key could be Name, Name + Version, Name + PublicKeyToken, or Name + Version + PublicKeyToken. + // The key could be Name; Name + Version; Name + PublicKeyToken; or Name + Version + PublicKeyToken. // Otherwise, update the cache with the newly loaded dll. - // First check Name. - UpdateCachedRMW(key, args.LoadedAssembly); + // First check the Name + UpdateCachedRMW(assemblyName, assembly); - string assemblyVersion = assemblyInfo.Version.ToString(); - - if (!String.IsNullOrEmpty(assemblyVersion)) + // While Version uses 32bit values for each respective field, RuntimeAssembly, AssemblyName nor metadata + // allows for AssemblyVersion (not to be confused with file) values bigger than UInt16.MaxValue - 1. + // Therefore our final length in chars is 5x4 for fields and 3x1 for the separators, 1x scratch space + Span scratchBuffer = stackalloc char[24]; + if (assemblyInfo.Version.TryFormat(scratchBuffer, out int charsWritten)) { - key = key + assemblyVersion; - // Check Name + Version - UpdateCachedRMW(key, args.LoadedAssembly); + assemblyNameVersion = $"{assemblyName}{scratchBuffer.Slice(0, charsWritten)}"; + UpdateCachedRMW(assemblyNameVersion, assembly); } byte[] publicKeyToken = assemblyInfo.GetPublicKeyToken(); - Span assemblyKey = stackalloc char[16]; - if (Convert.TryToHexStringLower(publicKeyToken, assemblyKey, out int charsWritten) && charsWritten == 16) + if (Convert.TryToHexStringLower(publicKeyToken, scratchBuffer, out charsWritten) && charsWritten == 16) { - key = $"{key}{assemblyKey}"; - // Check Name + Version + KeyToken - UpdateCachedRMW(key, args.LoadedAssembly); - - key = $"{assemblyName}{assemblyKey}"; + if (!string.IsNullOrEmpty(assemblyNameVersion)) + UpdateCachedRMW($"{assemblyNameVersion}{scratchBuffer.Slice(0, charsWritten)}", assembly); // Check Name + KeyToken - UpdateCachedRMW(key, args.LoadedAssembly); + UpdateCachedRMW($"{assemblyName}{scratchBuffer.Slice(0, charsWritten)}", assembly); } } } @@ -322,7 +319,7 @@ private static ResourceManagerWrapper GetResourceManagerWrapper(Uri uri, out str if (!String.IsNullOrEmpty(assemblyName)) { // Create the key, this will rarely get over 128 chars in my experience - string key = string.Create(null, stackalloc char [128], $"{assemblyName}{assemblyVersion}{assemblyKey}"); + string key = string.Create(null, stackalloc char[128], $"{assemblyName}{assemblyVersion}{assemblyKey}"); // first time. Add this to the hash table if (!s_registeredResourceManagers.TryGetValue(key, out rmwResult)) From 53976ea62106c5ac0ea8552966327a5a9bc6c7a6 Mon Sep 17 00:00:00 2001 From: h3xds1nz Date: Sun, 15 Sep 2024 14:39:25 +0200 Subject: [PATCH 10/17] Formatting changes only --- .../MS/Internal/AppModel/ResourceContainer.cs | 11 +++-------- 1 file changed, 3 insertions(+), 8 deletions(-) diff --git a/src/Microsoft.DotNet.Wpf/src/PresentationFramework/MS/Internal/AppModel/ResourceContainer.cs b/src/Microsoft.DotNet.Wpf/src/PresentationFramework/MS/Internal/AppModel/ResourceContainer.cs index b3bb6623d75..abd99d865e3 100644 --- a/src/Microsoft.DotNet.Wpf/src/PresentationFramework/MS/Internal/AppModel/ResourceContainer.cs +++ b/src/Microsoft.DotNet.Wpf/src/PresentationFramework/MS/Internal/AppModel/ResourceContainer.cs @@ -245,7 +245,7 @@ private void OnAssemblyLoadEventHandler(object sender, AssemblyLoadEventArgs arg // We do not care about assemblies loaded into the reflection-only context or the Gaced assemblies. // For example, in Sparkle whenever a project is built all dependent assemblies will be loaded reflection only. // We do no care about those. Only when a assembly is loaded into the execution context, we will need to update the cache. - if ((!assembly.ReflectionOnly)) + if (!assembly.ReflectionOnly) { AssemblyName assemblyInfo = new(assembly.FullName); @@ -307,14 +307,10 @@ private static void UpdateCachedRMW(string key, Assembly assembly) // private static ResourceManagerWrapper GetResourceManagerWrapper(Uri uri, out string partName, out bool isContentFile) { - string assemblyName; - string assemblyVersion; - string assemblyKey; ResourceManagerWrapper rmwResult = ApplicationResourceManagerWrapper; - isContentFile = false; - BaseUriHelper.GetAssemblyNameAndPart(uri, out partName, out assemblyName, out assemblyVersion, out assemblyKey); + BaseUriHelper.GetAssemblyNameAndPart(uri, out partName, out string assemblyName, out string assemblyVersion, out string assemblyKey); if (!String.IsNullOrEmpty(assemblyName)) { @@ -328,7 +324,6 @@ private static ResourceManagerWrapper GetResourceManagerWrapper(Uri uri, out str if (assembly.Equals(Application.ResourceAssembly)) { // This Uri maps to Application Entry assembly even though it has ";component". - rmwResult = ApplicationResourceManagerWrapper; } else @@ -340,7 +335,7 @@ private static ResourceManagerWrapper GetResourceManagerWrapper(Uri uri, out str } } - if ((rmwResult == ApplicationResourceManagerWrapper)) + if (rmwResult == ApplicationResourceManagerWrapper) { if (rmwResult != null) { From 11110fb4a2b76ab5de675964bbf20ef0426e0ad0 Mon Sep 17 00:00:00 2001 From: h3xds1nz Date: Sun, 15 Sep 2024 19:56:08 +0200 Subject: [PATCH 11/17] Shave off 300ns per invocation, allocation-free in 99.9% cases --- .../MS/Internal/AppModel/ResourceContainer.cs | 38 +++++++------- .../src/Shared/MS/Internal/ReflectionUtils.cs | 52 +++++++++++++++++-- 2 files changed, 69 insertions(+), 21 deletions(-) diff --git a/src/Microsoft.DotNet.Wpf/src/PresentationFramework/MS/Internal/AppModel/ResourceContainer.cs b/src/Microsoft.DotNet.Wpf/src/PresentationFramework/MS/Internal/AppModel/ResourceContainer.cs index abd99d865e3..58ab9e1874c 100644 --- a/src/Microsoft.DotNet.Wpf/src/PresentationFramework/MS/Internal/AppModel/ResourceContainer.cs +++ b/src/Microsoft.DotNet.Wpf/src/PresentationFramework/MS/Internal/AppModel/ResourceContainer.cs @@ -247,10 +247,12 @@ private void OnAssemblyLoadEventHandler(object sender, AssemblyLoadEventArgs arg // We do no care about those. Only when a assembly is loaded into the execution context, we will need to update the cache. if (!assembly.ReflectionOnly) { - AssemblyName assemblyInfo = new(assembly.FullName); + ReadOnlySpan assemblyName = ReflectionUtils.GetAssemblyPartialName(assembly); + ReflectionUtils.GetAssemblyVersionPlusToken(assembly, out ReadOnlySpan assemblyVersion, out ReadOnlySpan assemblyToken); + int totalLength = assemblyName.Length + assemblyVersion.Length + assemblyToken.Length; - string assemblyName = assemblyInfo.Name; - string assemblyNameVersion = null; + Span key = totalLength <= 256 ? stackalloc char[totalLength] : new char[totalLength]; + assemblyName.CopyTo(key); // Check if this newly loaded assembly is in the cache. If so, update the cache. // If it is not in cache, do not do anything. It will be added on demand. @@ -260,33 +262,32 @@ private void OnAssemblyLoadEventHandler(object sender, AssemblyLoadEventArgs arg // First check the Name UpdateCachedRMW(assemblyName, assembly); - // While Version uses 32bit values for each respective field, RuntimeAssembly, AssemblyName nor metadata - // allows for AssemblyVersion (not to be confused with file) values bigger than UInt16.MaxValue - 1. - // Therefore our final length in chars is 5x4 for fields and 3x1 for the separators, 1x scratch space - Span scratchBuffer = stackalloc char[24]; - if (assemblyInfo.Version.TryFormat(scratchBuffer, out int charsWritten)) + // Check Name + Version + if (!assemblyVersion.IsEmpty) { - // Check Name + Version - assemblyNameVersion = $"{assemblyName}{scratchBuffer.Slice(0, charsWritten)}"; - UpdateCachedRMW(assemblyNameVersion, assembly); + assemblyVersion.CopyTo(key.Slice(assemblyName.Length)); + UpdateCachedRMW(key.Slice(0, assemblyName.Length + assemblyVersion.Length), assembly); } - byte[] publicKeyToken = assemblyInfo.GetPublicKeyToken(); - if (Convert.TryToHexStringLower(publicKeyToken, scratchBuffer, out charsWritten) && charsWritten == 16) + if (!assemblyToken.IsEmpty) { // Check Name + Version + KeyToken - if (!string.IsNullOrEmpty(assemblyNameVersion)) - UpdateCachedRMW($"{assemblyNameVersion}{scratchBuffer.Slice(0, charsWritten)}", assembly); + if (!assemblyVersion.IsEmpty) + { + assemblyToken.CopyTo(key.Slice(assemblyName.Length + assemblyVersion.Length)); + UpdateCachedRMW(key.Slice(0, totalLength), assembly); + } // Check Name + KeyToken - UpdateCachedRMW($"{assemblyName}{scratchBuffer.Slice(0, charsWritten)}", assembly); + assemblyToken.CopyTo(key.Slice(assemblyName.Length)); + UpdateCachedRMW(key.Slice(0, assemblyName.Length + assemblyToken.Length), assembly); } } } - private static void UpdateCachedRMW(string key, Assembly assembly) + private static void UpdateCachedRMW(ReadOnlySpan key, Assembly assembly) { - if (s_registeredResourceManagers.TryGetValue(key, out ResourceManagerWrapper value)) + if (s_registeredResourceManagersLookup.TryGetValue(key, out ResourceManagerWrapper value)) { // Update the ResourceManagerWrapper with the new assembly. // Note Package caches Part and Part holds on to ResourceManagerWrapper. Package does not provide a way for @@ -368,6 +369,7 @@ private static ResourceManagerWrapper GetResourceManagerWrapper(Uri uri, out str #region Private Members private static readonly Dictionary s_registeredResourceManagers = new(StringComparer.OrdinalIgnoreCase); + private static readonly Dictionary.AlternateLookup> s_registeredResourceManagersLookup = s_registeredResourceManagers.GetAlternateLookup>(); private static readonly FileShare s_fileShare = FileShare.Read; private static ResourceManagerWrapper s_applicationResourceManagerWrapper = null; diff --git a/src/Microsoft.DotNet.Wpf/src/Shared/MS/Internal/ReflectionUtils.cs b/src/Microsoft.DotNet.Wpf/src/Shared/MS/Internal/ReflectionUtils.cs index ef7fa223ce6..15d351eaf0e 100644 --- a/src/Microsoft.DotNet.Wpf/src/Shared/MS/Internal/ReflectionUtils.cs +++ b/src/Microsoft.DotNet.Wpf/src/Shared/MS/Internal/ReflectionUtils.cs @@ -7,6 +7,7 @@ using System.Runtime.CompilerServices; using System.Reflection.Metadata; using System.Reflection; +using System.Text; using System; namespace MS.Internal @@ -17,9 +18,12 @@ namespace MS.Internal internal static class ReflectionUtils { #if !NETFX + private const string Version = ", Version="; + private const string PublicKeyToken = ", PublicKeyToken="; + /// - /// Retrieves the full assembly name by combining the passed in - /// with everything else from . + /// Retrieves the full assembly name by combining the passed in + /// with everything else from . /// internal static string GetFullAssemblyNameFromPartialName(Assembly assembly, string partialName) { @@ -33,7 +37,7 @@ internal static string GetFullAssemblyNameFromPartialName(Assembly assembly, str #endif /// - /// Given an , returns the partial/simple name of the assembly. + /// Given an , returns the partial/simple name of the assembly. /// #if !NETFX internal static ReadOnlySpan GetAssemblyPartialName(Assembly assembly) @@ -80,5 +84,47 @@ static void UnescapeDirty(ref ReadOnlySpan dirtyName) return name.Name ?? string.Empty; #endif } + +#if !NETFX + /// + /// Parses and retrieves "Version" and "PublicKeyToken" values from the original string. + /// This should only be passed a RuntimeAssembly to ensure proper functionality. + /// + /// The RuntimeAssembly which will provide properly formatted full name. + /// If present, returns the value of Version portion, otherwise Empty result. + /// If present, returns the value of PublicKeyToken portion. Empty result is returned when the value is "null" or not present. + internal static void GetAssemblyVersionPlusToken(Assembly assembly, out ReadOnlySpan assemblyVersion, out ReadOnlySpan assemblyToken) + { + ArgumentNullException.ThrowIfNull(assembly, nameof(assembly)); + ReadOnlySpan assemblyName = assembly.FullName; + + assemblyVersion = ReadOnlySpan.Empty; + assemblyToken = ReadOnlySpan.Empty; + + // Parse Version section + int versionIndex = assemblyName.IndexOf(Version); + if (versionIndex != -1) + { + int tokenEnding = assemblyName.Slice(versionIndex + 1).IndexOf(',') + 1; + int tokenLength = tokenEnding == 0 ? assemblyName.Slice(versionIndex).Length : tokenEnding; + + assemblyVersion = assemblyName.Slice(versionIndex + Version.Length, tokenLength - Version.Length); + } + + // Parse PublicKeyToken section + int tokenIndex = assemblyName.IndexOf(PublicKeyToken); + if (tokenIndex != -1) + { + int tokenEnding = assemblyName.Slice(tokenIndex + 1).IndexOf(',') + 1; + int tokenLength = tokenEnding == 0 ? assemblyName.Slice(tokenIndex).Length : tokenEnding; + + // PublicKeyToken is always 8 bytes (16 chars in HEX), in other cases it is gonna be "null", + // however it is simply faster to match it via Length as original parser does it than anything else + assemblyToken = assemblyName.Slice(tokenIndex + PublicKeyToken.Length, tokenLength - PublicKeyToken.Length); + if (assemblyToken.Length != 16) + assemblyToken = ReadOnlySpan.Empty; + } + } +#endif } } From 3390a84c0922e9cecee7dcc6fd70384ab047a214 Mon Sep 17 00:00:00 2001 From: h3xds1nz Date: Thu, 19 Sep 2024 10:10:52 +0200 Subject: [PATCH 12/17] Seal the class, remove redundant usings, final formatting changes/comments only --- .../MS/Internal/AppModel/ResourceContainer.cs | 168 ++++++------------ 1 file changed, 59 insertions(+), 109 deletions(-) diff --git a/src/Microsoft.DotNet.Wpf/src/PresentationFramework/MS/Internal/AppModel/ResourceContainer.cs b/src/Microsoft.DotNet.Wpf/src/PresentationFramework/MS/Internal/AppModel/ResourceContainer.cs index 58ab9e1874c..6cecddd4826 100644 --- a/src/Microsoft.DotNet.Wpf/src/PresentationFramework/MS/Internal/AppModel/ResourceContainer.cs +++ b/src/Microsoft.DotNet.Wpf/src/PresentationFramework/MS/Internal/AppModel/ResourceContainer.cs @@ -17,19 +17,23 @@ using System.Reflection; using System.Globalization; using System.Windows; +using System.Reflection; +using System.IO.Packaging; using System.Windows.Navigation; +using System.Collections.Generic; + using MS.Internal.Resources; namespace MS.Internal.AppModel { - // - // ResourceContainer is an implementation of the abstract Package class. - // It contains nontrivial overrides for GetPartCore and Exists. - // Many of the methods on Package are not applicable to loading application - // resources, so the ResourceContainer implementations of these methods throw - // the NotSupportedException. - // - internal class ResourceContainer : System.IO.Packaging.Package + /// + /// ResourceContainer is an implementation of the abstract Package class. + /// It contains nontrivial overrides for GetPartCore and Exists. + /// Many of the methods on Package are not applicable to loading application + /// resources, so the ResourceContainer implementations of these methods throw + /// the NotSupportedException. + /// + internal sealed class ResourceContainer : Package { //------------------------------------------------------ // @@ -39,7 +43,6 @@ internal class ResourceContainer : System.IO.Packaging.Package #region Static Methods - internal static ResourceManagerWrapper ApplicationResourceManagerWrapper { get @@ -59,11 +62,10 @@ internal static ResourceManagerWrapper ApplicationResourceManagerWrapper } } - // - // The FileShare mode to use for opening loose files. Currently this defaults to FileShare.Read - // Today it is not changed. If we decide that this should change in the future we can easily add - // a seter here. - // + /// + /// The FileShare mode to use for opening loose files. Currently this defaults to + /// Today it is not changed. If we decide that this should change in the future we can easily add a setter here. + /// internal static FileShare FileShare { get @@ -82,41 +84,31 @@ internal static FileShare FileShare #region Public Constructors - // - // Default Constructor - // - internal ResourceContainer() : base(FileAccess.Read) - { - } + /// + /// Default Constructor + /// + internal ResourceContainer() : base(FileAccess.Read) { } - #endregion - - //------------------------------------------------------ - // - // Public Properties - // - //------------------------------------------------------ - // None + #endregion //------------------------------------------------------ // // Public Methods // //------------------------------------------------------ - #region Public Methods - // - // This method always returns true. This is because ResourceManager does not have a - // simple way to check if a resource exists without loading the resource stream (or failing to) - // so checking if a resource exists would be a very expensive task. - // A part will later be constructed and returned by GetPart(). This part class contains - // a ResourceManager which may or may not contain the requested resource. When someone - // calls GetStream() on PackagePart then we will attempt to get the stream for the named resource - // and potentially fail. - // - // - // + /// + /// This method always returns true. This is because ResourceManager does not have a + /// simple way to check if a resource exists without loading the resource stream (or failing to) + /// so checking if a resource exists would be a very expensive task. + /// A part will later be constructed and returned by GetPart(). This part class contains + /// a ResourceManager which may or may not contain the requested resource. When someone + /// calls GetStream() on PackagePart then we will attempt to get the stream for the named resource + /// and potentially fail. + /// + /// + /// public override bool PartExists(Uri uri) { return true; @@ -124,26 +116,11 @@ public override bool PartExists(Uri uri) #endregion - //------------------------------------------------------ - // - // Public Events - // - //------------------------------------------------------ - // None - - //------------------------------------------------------ - // - // Internal Constructors - // - //------------------------------------------------------ - // None - //------------------------------------------------------ // // Internal Properties // //------------------------------------------------------ - #region Internal Members internal const string XamlExt = ".xaml"; @@ -151,72 +128,45 @@ public override bool PartExists(Uri uri) #endregion - - //------------------------------------------------------ - // - // Internal Methods - // - //------------------------------------------------------ - // None - - //------------------------------------------------------ - // - // Internal Events - // - //------------------------------------------------------ - // None - - //------------------------------------------------------ - // - // Protected Constructors - // - //------------------------------------------------------ - // None - //------------------------------------------------------ // // Protected Methods // //------------------------------------------------------ - #region Protected Methods - // - // This method creates a part containing the name of the resource and - // the resource manager that should contain it. If the resource manager - // does not contain the requested part then when GetStream() is called on - // the part it will return null. - // - // - // - + /// + /// This method creates a part containing the name of the resource and + /// the resource manager that should contain it. If the resource manager + /// does not contain the requested part then when GetStream() is called on + /// the part it will return null. + /// + /// + /// protected override PackagePart GetPartCore(Uri uri) { - string partName; - bool isContentFile; - // AppDomain.AssemblyLoad event handler for standalone apps. This is added specifically for designer (Sparkle) scenario. // We use the assembly name to fetch the cached resource manager. With this mechanism we will still get resource from the // old version dll when a newer one is loaded. So whenever the AssemblyLoad event is fired, we will need to update the cache // with the newly loaded assembly. This is currently only for designer so not needed for browser hosted apps. // Attach the event handler before the first time we get the ResourceManagerWrapper. - if (!s_assemblyLoadhandlerAttached) + if (!s_assemblyLoadHandlerAttached) { AppDomain.CurrentDomain.AssemblyLoad += new AssemblyLoadEventHandler(OnAssemblyLoadEventHandler); - s_assemblyLoadhandlerAttached = true; + s_assemblyLoadHandlerAttached = true; } - ResourceManagerWrapper rmWrapper = GetResourceManagerWrapper(uri, out partName, out isContentFile); + ResourceManagerWrapper rmWrapper = GetResourceManagerWrapper(uri, out string partName, out bool isContentFile); // If the part name was specified as Content at compile time then we will try to load - // the file directly. Otherwise we assume the user is looking for a resource. + // the file directly. Otherwise we assume the user is looking for a resource. if (isContentFile) { return new ContentFilePart(this, uri); } else { - // Uri mapps to a resource stream. + // Uri maps to a resource stream. // Make sure the resource id is exactly same as the one we used to create Resource // at compile time. @@ -236,13 +186,13 @@ protected override PackagePart GetPartCore(Uri uri) #region Private Methods - // AppDomain.AssemblyLoad event handler. Check whether the assembly's resourcemanager has + // AppDomain.AssemblyLoad event handler. Check whether the assembly's ResourceManager has // been added to the cache. If it has, we need to update the cache with the newly loaded dll. private void OnAssemblyLoadEventHandler(object sender, AssemblyLoadEventArgs args) { Assembly assembly = args.LoadedAssembly; // This is specific for designer (Sparkle) scenario: rebuild and reload dll using Load(Byte[]). - // We do not care about assemblies loaded into the reflection-only context or the Gaced assemblies. + // We do not care about assemblies loaded into the reflection-only context or the GACed assemblies. // For example, in Sparkle whenever a project is built all dependent assemblies will be loaded reflection only. // We do no care about those. Only when a assembly is loaded into the execution context, we will need to update the cache. if (!assembly.ReflectionOnly) @@ -259,7 +209,7 @@ private void OnAssemblyLoadEventHandler(object sender, AssemblyLoadEventArgs arg // The key could be Name; Name + Version; Name + PublicKeyToken; or Name + Version + PublicKeyToken. // Otherwise, update the cache with the newly loaded dll. - // First check the Name + // Firstly, check the Name UpdateCachedRMW(assemblyName, assembly); // Check Name + Version @@ -297,15 +247,15 @@ private static void UpdateCachedRMW(ReadOnlySpan key, Assembly assembly) } } - // - // Searches the available ResourceManagerWrapper list for one that matches the given Uri. - // It could be either ResourceManagerWrapper for specific libary assembly or Application - // main assembly. Package enforces that all Uri will be correctly formated. - // - // Assumed to be relative - // The name of the file in the resource manager - // A flag to indicate that this path is a known loose file at compile time - // + /// + /// Searches the available ResourceManagerWrapper list for one that matches the given Uri. + /// It could be either ResourceManagerWrapper for specific library assembly or Application + /// main assembly. Package enforces that all Uri will be correctly formatted. + /// + /// Assumed to be relative + /// The name of the file in the resource manager + /// A flag to indicate that this path is a known loose file at compile time + /// private static ResourceManagerWrapper GetResourceManagerWrapper(Uri uri, out string partName, out bool isContentFile) { ResourceManagerWrapper rmwResult = ApplicationResourceManagerWrapper; @@ -313,7 +263,7 @@ private static ResourceManagerWrapper GetResourceManagerWrapper(Uri uri, out str BaseUriHelper.GetAssemblyNameAndPart(uri, out partName, out string assemblyName, out string assemblyVersion, out string assemblyKey); - if (!String.IsNullOrEmpty(assemblyName)) + if (!string.IsNullOrEmpty(assemblyName)) { // Create the key, this will rarely get over 128 chars in my experience string key = string.Create(null, stackalloc char[128], $"{assemblyName}{assemblyVersion}{assemblyKey}"); @@ -373,7 +323,7 @@ private static ResourceManagerWrapper GetResourceManagerWrapper(Uri uri, out str private static readonly FileShare s_fileShare = FileShare.Read; private static ResourceManagerWrapper s_applicationResourceManagerWrapper = null; - private static bool s_assemblyLoadhandlerAttached = false; + private static bool s_assemblyLoadHandlerAttached = false; #endregion Private Members From 4c01ae1943c96ac9eae103017e5fd5c45e0cd6a9 Mon Sep 17 00:00:00 2001 From: h3xds1nz Date: Thu, 19 Sep 2024 10:25:25 +0200 Subject: [PATCH 13/17] GetResourceManagerWrapper is mostly just using key for lookup, no reason for string --- .../MS/Internal/AppModel/ResourceContainer.cs | 18 +++++++++++------- 1 file changed, 11 insertions(+), 7 deletions(-) diff --git a/src/Microsoft.DotNet.Wpf/src/PresentationFramework/MS/Internal/AppModel/ResourceContainer.cs b/src/Microsoft.DotNet.Wpf/src/PresentationFramework/MS/Internal/AppModel/ResourceContainer.cs index 6cecddd4826..c6606952006 100644 --- a/src/Microsoft.DotNet.Wpf/src/PresentationFramework/MS/Internal/AppModel/ResourceContainer.cs +++ b/src/Microsoft.DotNet.Wpf/src/PresentationFramework/MS/Internal/AppModel/ResourceContainer.cs @@ -261,17 +261,21 @@ private static ResourceManagerWrapper GetResourceManagerWrapper(Uri uri, out str ResourceManagerWrapper rmwResult = ApplicationResourceManagerWrapper; isContentFile = false; - BaseUriHelper.GetAssemblyNameAndPart(uri, out partName, out string assemblyName, out string assemblyVersion, out string assemblyKey); + BaseUriHelper.GetAssemblyNameAndPart(uri, out partName, out string assemblyName, out string assemblyVersion, out string assemblyToken); if (!string.IsNullOrEmpty(assemblyName)) { - // Create the key, this will rarely get over 128 chars in my experience - string key = string.Create(null, stackalloc char[128], $"{assemblyName}{assemblyVersion}{assemblyKey}"); + // Create the key, in format of $"{assemblyName}{assemblyVersion}{assemblyToken}" + int totalLength = assemblyName.Length + assemblyVersion.Length + assemblyToken.Length; + Span key = totalLength <= 256 ? stackalloc char[totalLength] : new char[totalLength]; + assemblyName.CopyTo(key); + assemblyVersion.CopyTo(key.Slice(assemblyName.Length)); + assemblyToken.CopyTo(key.Slice(assemblyName.Length + assemblyVersion.Length)); - // first time. Add this to the hash table - if (!s_registeredResourceManagers.TryGetValue(key, out rmwResult)) + // First time; add this to the dictionary and create the wrapper if its not the application entry assembly + if (!s_registeredResourceManagersLookup.TryGetValue(key.Slice(0, totalLength), out rmwResult)) { - Assembly assembly = BaseUriHelper.GetLoadedAssembly(assemblyName, assemblyVersion, assemblyKey); + Assembly assembly = BaseUriHelper.GetLoadedAssembly(assemblyName, assemblyVersion, assemblyToken); if (assembly.Equals(Application.ResourceAssembly)) { // This Uri maps to Application Entry assembly even though it has ";component". @@ -282,7 +286,7 @@ private static ResourceManagerWrapper GetResourceManagerWrapper(Uri uri, out str rmwResult = new ResourceManagerWrapper(assembly); } - s_registeredResourceManagers[key] = rmwResult; + s_registeredResourceManagersLookup[key.Slice(0, totalLength)] = rmwResult; } } From 79ed81cb48aaff92934422f43741a78b6a1f5833 Mon Sep 17 00:00:00 2001 From: h3xds1nz Date: Fri, 20 Sep 2024 01:10:23 +0200 Subject: [PATCH 14/17] Revert the StringComparer.OrdinalIgnoreCase usage because of Span buffer --- .../MS/Internal/AppModel/ResourceContainer.cs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/Microsoft.DotNet.Wpf/src/PresentationFramework/MS/Internal/AppModel/ResourceContainer.cs b/src/Microsoft.DotNet.Wpf/src/PresentationFramework/MS/Internal/AppModel/ResourceContainer.cs index c6606952006..2849354fb47 100644 --- a/src/Microsoft.DotNet.Wpf/src/PresentationFramework/MS/Internal/AppModel/ResourceContainer.cs +++ b/src/Microsoft.DotNet.Wpf/src/PresentationFramework/MS/Internal/AppModel/ResourceContainer.cs @@ -202,7 +202,7 @@ private void OnAssemblyLoadEventHandler(object sender, AssemblyLoadEventArgs arg int totalLength = assemblyName.Length + assemblyVersion.Length + assemblyToken.Length; Span key = totalLength <= 256 ? stackalloc char[totalLength] : new char[totalLength]; - assemblyName.CopyTo(key); + assemblyName.ToLowerInvariant(key); // Check if this newly loaded assembly is in the cache. If so, update the cache. // If it is not in cache, do not do anything. It will be added on demand. @@ -210,7 +210,7 @@ private void OnAssemblyLoadEventHandler(object sender, AssemblyLoadEventArgs arg // Otherwise, update the cache with the newly loaded dll. // Firstly, check the Name - UpdateCachedRMW(assemblyName, assembly); + UpdateCachedRMW(key.Slice(0, assemblyName.Length), assembly); // Check Name + Version if (!assemblyVersion.IsEmpty) @@ -268,7 +268,7 @@ private static ResourceManagerWrapper GetResourceManagerWrapper(Uri uri, out str // Create the key, in format of $"{assemblyName}{assemblyVersion}{assemblyToken}" int totalLength = assemblyName.Length + assemblyVersion.Length + assemblyToken.Length; Span key = totalLength <= 256 ? stackalloc char[totalLength] : new char[totalLength]; - assemblyName.CopyTo(key); + assemblyName.AsSpan().ToLowerInvariant(key); assemblyVersion.CopyTo(key.Slice(assemblyName.Length)); assemblyToken.CopyTo(key.Slice(assemblyName.Length + assemblyVersion.Length)); @@ -322,7 +322,7 @@ private static ResourceManagerWrapper GetResourceManagerWrapper(Uri uri, out str #region Private Members - private static readonly Dictionary s_registeredResourceManagers = new(StringComparer.OrdinalIgnoreCase); + private static readonly Dictionary s_registeredResourceManagers = new(StringComparer.Ordinal); private static readonly Dictionary.AlternateLookup> s_registeredResourceManagersLookup = s_registeredResourceManagers.GetAlternateLookup>(); private static readonly FileShare s_fileShare = FileShare.Read; From f1954a008f9abbafd7ea9a3752d5645b2a4016a7 Mon Sep 17 00:00:00 2001 From: h3xds1nz Date: Fri, 20 Sep 2024 11:28:45 +0200 Subject: [PATCH 15/17] Fix original comment typo --- .../MS/Internal/AppModel/ResourceContainer.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Microsoft.DotNet.Wpf/src/PresentationFramework/MS/Internal/AppModel/ResourceContainer.cs b/src/Microsoft.DotNet.Wpf/src/PresentationFramework/MS/Internal/AppModel/ResourceContainer.cs index 2849354fb47..8ccd53b9aec 100644 --- a/src/Microsoft.DotNet.Wpf/src/PresentationFramework/MS/Internal/AppModel/ResourceContainer.cs +++ b/src/Microsoft.DotNet.Wpf/src/PresentationFramework/MS/Internal/AppModel/ResourceContainer.cs @@ -49,7 +49,7 @@ internal static ResourceManagerWrapper ApplicationResourceManagerWrapper { if (s_applicationResourceManagerWrapper == null) { - // load main excutable assembly + // load main executable assembly Assembly asmApplication = Application.ResourceAssembly; if (asmApplication != null) From 8dd6d06d9d107c52153d9e150604eeceb73bab4a Mon Sep 17 00:00:00 2001 From: h3xds1nz Date: Mon, 23 Sep 2024 18:43:25 +0200 Subject: [PATCH 16/17] Fix a few more comments, revert list separator --- .../MS/Internal/AppModel/ResourceContainer.cs | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/src/Microsoft.DotNet.Wpf/src/PresentationFramework/MS/Internal/AppModel/ResourceContainer.cs b/src/Microsoft.DotNet.Wpf/src/PresentationFramework/MS/Internal/AppModel/ResourceContainer.cs index 8ccd53b9aec..bbab2b3a26f 100644 --- a/src/Microsoft.DotNet.Wpf/src/PresentationFramework/MS/Internal/AppModel/ResourceContainer.cs +++ b/src/Microsoft.DotNet.Wpf/src/PresentationFramework/MS/Internal/AppModel/ResourceContainer.cs @@ -95,7 +95,8 @@ internal ResourceContainer() : base(FileAccess.Read) { } // // Public Methods // - //------------------------------------------------------ + //------------------------------------------------------ + #region Public Methods /// @@ -118,10 +119,11 @@ public override bool PartExists(Uri uri) //------------------------------------------------------ // - // Internal Properties + // Internal Constants // //------------------------------------------------------ - #region Internal Members + + #region Internal Constants internal const string XamlExt = ".xaml"; internal const string BamlExt = ".baml"; @@ -133,6 +135,7 @@ public override bool PartExists(Uri uri) // Protected Methods // //------------------------------------------------------ + #region Protected Methods /// @@ -206,7 +209,7 @@ private void OnAssemblyLoadEventHandler(object sender, AssemblyLoadEventArgs arg // Check if this newly loaded assembly is in the cache. If so, update the cache. // If it is not in cache, do not do anything. It will be added on demand. - // The key could be Name; Name + Version; Name + PublicKeyToken; or Name + Version + PublicKeyToken. + // The key could be Name, Name + Version, Name + PublicKeyToken, or Name + Version + PublicKeyToken. // Otherwise, update the cache with the newly loaded dll. // Firstly, check the Name From 0d1084d5129b613f686532601f8194cada46bf3f Mon Sep 17 00:00:00 2001 From: h3xds1nz Date: Wed, 22 Jan 2025 10:47:05 +0100 Subject: [PATCH 17/17] Fix usings post-merge --- .../MS/Internal/AppModel/ResourceContainer.cs | 14 ++++---------- .../src/Shared/MS/Internal/ReflectionUtils.cs | 1 - 2 files changed, 4 insertions(+), 11 deletions(-) diff --git a/src/Microsoft.DotNet.Wpf/src/PresentationFramework/MS/Internal/AppModel/ResourceContainer.cs b/src/Microsoft.DotNet.Wpf/src/PresentationFramework/MS/Internal/AppModel/ResourceContainer.cs index bbab2b3a26f..92e340be325 100644 --- a/src/Microsoft.DotNet.Wpf/src/PresentationFramework/MS/Internal/AppModel/ResourceContainer.cs +++ b/src/Microsoft.DotNet.Wpf/src/PresentationFramework/MS/Internal/AppModel/ResourceContainer.cs @@ -1,6 +1,5 @@ -// Licensed to the .NET Foundation under one or more agreements. +// Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. -// See the LICENSE file in the project root for more information. // // @@ -12,17 +11,12 @@ // the NotSupportedException. // +using System.Windows.Navigation; +using MS.Internal.Resources; using System.IO.Packaging; -using System.IO; using System.Reflection; -using System.Globalization; using System.Windows; -using System.Reflection; -using System.IO.Packaging; -using System.Windows.Navigation; -using System.Collections.Generic; - -using MS.Internal.Resources; +using System.IO; namespace MS.Internal.AppModel { diff --git a/src/Microsoft.DotNet.Wpf/src/Shared/MS/Internal/ReflectionUtils.cs b/src/Microsoft.DotNet.Wpf/src/Shared/MS/Internal/ReflectionUtils.cs index 15d351eaf0e..336b04e1fdc 100644 --- a/src/Microsoft.DotNet.Wpf/src/Shared/MS/Internal/ReflectionUtils.cs +++ b/src/Microsoft.DotNet.Wpf/src/Shared/MS/Internal/ReflectionUtils.cs @@ -1,6 +1,5 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. -// See the LICENSE file in the project root for more information. #nullable enable