From fa409a8186996e56938b276aad0fd4eee9d05793 Mon Sep 17 00:00:00 2001
From: Snorri Gislason <snbeck@microsoft.com>
Date: Fri, 7 Feb 2025 03:29:25 +0000
Subject: [PATCH 01/49] Load resource stream outside of delegate

---
 ILSpy/TextView/DecompilerTextView.cs | 23 +++++++++++++----------
 1 file changed, 13 insertions(+), 10 deletions(-)

diff --git a/ILSpy/TextView/DecompilerTextView.cs b/ILSpy/TextView/DecompilerTextView.cs
index 4e748fd2a..3b9ea493c 100644
--- a/ILSpy/TextView/DecompilerTextView.cs
+++ b/ILSpy/TextView/DecompilerTextView.cs
@@ -1447,17 +1447,20 @@ namespace ICSharpCode.ILSpy.TextView
 
 			if (resourceStream != null)
 			{
+				IHighlightingDefinition highlightingDefinition;
+
+				using (resourceStream)
+				using (XmlTextReader reader = new XmlTextReader(resourceStream))
+				{
+					highlightingDefinition = HighlightingLoader.Load(reader, manager);
+				}
+
 				manager.RegisterHighlighting(
-					name, extensions,
-					delegate {
-						using (resourceStream)
-						using (XmlTextReader reader = new XmlTextReader(resourceStream))
-						{
-							var highlightingDefinition = HighlightingLoader.Load(reader, manager);
-							ThemeManager.Current.ApplyHighlightingColors(highlightingDefinition);
-							return highlightingDefinition;
-						}
-					});
+				name, extensions,
+				delegate {
+					ThemeManager.Current.ApplyHighlightingColors(highlightingDefinition);
+					return highlightingDefinition;
+				});
 			}
 		}
 	}

From 15b826fb7060a648088bc3e3cd3b20ee90521c38 Mon Sep 17 00:00:00 2001
From: nil4 <nil4@users.noreply.github.com>
Date: Sun, 9 Feb 2025 10:41:16 +0100
Subject: [PATCH 02/49] Enable `<RollForward>major</RollForward>` for
 `ILSpy.exe`

Allow `ILSpy.exe` to run when .NET 8 (the version it currently targets)
is not installed, but a later major version, e.g. .NET 9, is available.

ref. https://learn.microsoft.com/en-us/dotnet/core/versions/selection#control-roll-forward-behavior

ref. https://github.com/icsharpcode/ILSpy/issues/3390
---
 ILSpy/ILSpy.csproj | 1 +
 1 file changed, 1 insertion(+)

diff --git a/ILSpy/ILSpy.csproj b/ILSpy/ILSpy.csproj
index ba2e33f5b..aa5a7264a 100644
--- a/ILSpy/ILSpy.csproj
+++ b/ILSpy/ILSpy.csproj
@@ -3,6 +3,7 @@
   <PropertyGroup>
     <OutputType>WinExe</OutputType>
     <TargetFramework>net8.0-windows</TargetFramework>
+    <RollForward>major</RollForward>
     <RuntimeIdentifiers>win-x64;win-arm64</RuntimeIdentifiers>
     <GenerateAssemblyInfo>False</GenerateAssemblyInfo>
     <AutoGenerateBindingRedirects>false</AutoGenerateBindingRedirects>

From fa50e8d8b3602a8b56bef2f06b8f7024be28440a Mon Sep 17 00:00:00 2001
From: Siegfried Pammer <siegfriedpammer@gmail.com>
Date: Wed, 12 Feb 2025 22:53:22 +0100
Subject: [PATCH 03/49] Add non-embedded attributes to all tests that use older
 framework versions.

---
 .../Helpers/Tester.cs                         | 45 ++++++++++++++++++-
 .../ICSharpCode.Decompiler.Tests.csproj       |  2 +-
 .../TestCases/Pretty/Records.cs               | 28 +-----------
 .../TestCases/Pretty/Structs.cs               | 18 +-------
 .../OutputVisitor/CSharpOutputVisitor.cs      |  4 +-
 .../Transforms/EscapeInvalidIdentifiers.cs    | 18 +++++++-
 6 files changed, 66 insertions(+), 49 deletions(-)

diff --git a/ICSharpCode.Decompiler.Tests/Helpers/Tester.cs b/ICSharpCode.Decompiler.Tests/Helpers/Tester.cs
index bdea4570f..ed9e2739f 100644
--- a/ICSharpCode.Decompiler.Tests/Helpers/Tester.cs
+++ b/ICSharpCode.Decompiler.Tests/Helpers/Tester.cs
@@ -18,7 +18,6 @@
 
 using System;
 using System.Collections.Generic;
-using System.Diagnostics;
 using System.IO;
 using System.Linq;
 using System.Reflection.PortableExecutable;
@@ -330,6 +329,45 @@ namespace ICSharpCode.Decompiler.Tests.Helpers
 			return tempFile;
 		}
 
+		const string nonEmbeddedAttributesSnippet = @"
+using System;
+
+#if !NET60
+namespace System.Runtime.CompilerServices
+{
+	[AttributeUsage(AttributeTargets.All, AllowMultiple = true, Inherited = false)]
+	internal sealed class CompilerFeatureRequiredAttribute : Attribute
+	{
+		public CompilerFeatureRequiredAttribute(string featureName)
+		{
+		}
+	}
+
+	internal class IsExternalInit
+	{
+	}
+#endif
+#if !NET70
+	[AttributeUsage(AttributeTargets.Class | AttributeTargets.Struct | AttributeTargets.Property | AttributeTargets.Field, AllowMultiple = false, Inherited = false)]
+	internal sealed class RequiredMemberAttribute : Attribute
+	{
+	}
+#endif
+#if !NET60
+}
+#endif
+";
+
+		static readonly Lazy<string> nonEmbeddedAttributesSnippetFile = new Lazy<string>(GetNonEmbeddedAttributesSnippetFile);
+
+		static string GetNonEmbeddedAttributesSnippetFile()
+		{
+			// Note: this leaks a temporary file, we're not attempting to delete it, because it is only one.
+			var tempFile = Path.GetTempFileName();
+			File.WriteAllText(tempFile, nonEmbeddedAttributesSnippet);
+			return tempFile;
+		}
+
 		public static List<string> GetPreprocessorSymbols(CompilerOptions flags)
 		{
 			var preprocessorSymbols = new List<string>();
@@ -419,6 +457,11 @@ namespace ICSharpCode.Decompiler.Tests.Helpers
 				sourceFileNames.Add(targetFrameworkAttributeSnippetFile.Value);
 			}
 
+			if (targetNet40)
+			{
+				sourceFileNames.Add(nonEmbeddedAttributesSnippetFile.Value);
+			}
+
 			var preprocessorSymbols = GetPreprocessorSymbols(flags);
 
 			if ((flags & CompilerOptions.UseMcsMask) == 0)
diff --git a/ICSharpCode.Decompiler.Tests/ICSharpCode.Decompiler.Tests.csproj b/ICSharpCode.Decompiler.Tests/ICSharpCode.Decompiler.Tests.csproj
index 305d22d96..0ec42ecbe 100644
--- a/ICSharpCode.Decompiler.Tests/ICSharpCode.Decompiler.Tests.csproj
+++ b/ICSharpCode.Decompiler.Tests/ICSharpCode.Decompiler.Tests.csproj
@@ -151,7 +151,7 @@
     <None Include="TestCases\Pretty\CovariantReturns.cs" />
     <Compile Include="TestCases\VBPretty\VBPropertiesTest.cs" />
     <None Include="TestCases\ILPretty\Issue2260SwitchString.cs" />
-    <None Include="TestCases\Pretty\Records.cs" />
+    <Compile Include="TestCases\Pretty\Records.cs" />
     <Compile Include="TestCases\VBPretty\Issue2192.cs" />
     <Compile Include="Util\FileUtilityTests.cs" />
     <Compile Include="TestCases\Pretty\FunctionPointers.cs" />
diff --git a/ICSharpCode.Decompiler.Tests/TestCases/Pretty/Records.cs b/ICSharpCode.Decompiler.Tests/TestCases/Pretty/Records.cs
index 49a4fba3e..f6937aa02 100644
--- a/ICSharpCode.Decompiler.Tests/TestCases/Pretty/Records.cs
+++ b/ICSharpCode.Decompiler.Tests/TestCases/Pretty/Records.cs
@@ -44,7 +44,7 @@ namespace ICSharpCode.Decompiler.Tests.TestCases.Pretty
 		public record PairWithPrimaryCtor<A, B>(A First, B Second);
 
 		public record PrimaryCtor(int A, string B);
-		public record PrimaryCtorWithAttribute([RecordTest("param")] [property: RecordTest("property")][field: RecordTest("field")] int a);
+		public record PrimaryCtorWithAttribute([RecordTest("param")][property: RecordTest("property")][field: RecordTest("field")] int a);
 		public record PrimaryCtorWithField(int A, string B)
 		{
 			public double C = 1.0;
@@ -169,7 +169,7 @@ namespace ICSharpCode.Decompiler.Tests.TestCases.Pretty
 		public record struct PairWithPrimaryCtor<A, B>(A First, B Second);
 
 		public record struct PrimaryCtor(int A, string B);
-		public record struct PrimaryCtorWithAttribute([RecordTest("param")] [property: RecordTest("property")][field: RecordTest("field")] int a);
+		public record struct PrimaryCtorWithAttribute([RecordTest("param")][property: RecordTest("property")][field: RecordTest("field")] int a);
 		public record struct PrimaryCtorWithField(int A, string B)
 		{
 			public double C = 1.0;
@@ -242,27 +242,3 @@ namespace ICSharpCode.Decompiler.Tests.TestCases.Pretty
 	}
 #endif
 }
-#if !NET60
-namespace System.Runtime.CompilerServices
-{
-	[AttributeUsage(AttributeTargets.All, AllowMultiple = true, Inherited = false)]
-	internal sealed class CompilerFeatureRequiredAttribute : Attribute
-	{
-		public CompilerFeatureRequiredAttribute(string featureName)
-		{
-		}
-	}
-
-	internal class IsExternalInit
-	{
-	}
-#endif
-#if !NET70
-	[AttributeUsage(AttributeTargets.Class | AttributeTargets.Struct | AttributeTargets.Property | AttributeTargets.Field, AllowMultiple = false, Inherited = false)]
-	internal sealed class RequiredMemberAttribute : Attribute
-	{
-	}
-#endif
-#if !NET60
-}
-#endif
diff --git a/ICSharpCode.Decompiler.Tests/TestCases/Pretty/Structs.cs b/ICSharpCode.Decompiler.Tests/TestCases/Pretty/Structs.cs
index c796990eb..1e90c8382 100644
--- a/ICSharpCode.Decompiler.Tests/TestCases/Pretty/Structs.cs
+++ b/ICSharpCode.Decompiler.Tests/TestCases/Pretty/Structs.cs
@@ -59,20 +59,4 @@ namespace ICSharpCode.Decompiler.Tests.TestCases.Pretty
 		public required string LastName { get; set; }
 	}
 #endif
-}
-
-namespace System.Runtime.CompilerServices
-{
-	[AttributeUsage(AttributeTargets.All, AllowMultiple = true, Inherited = false)]
-	internal sealed class CompilerFeatureRequiredAttribute : Attribute
-	{
-		public CompilerFeatureRequiredAttribute(string featureName)
-		{
-		}
-	}
-
-	[AttributeUsage(AttributeTargets.Class | AttributeTargets.Struct | AttributeTargets.Property | AttributeTargets.Field, AllowMultiple = false, Inherited = false)]
-	internal sealed class RequiredMemberAttribute : Attribute
-	{
-	}
-}
+}
\ No newline at end of file
diff --git a/ICSharpCode.Decompiler/CSharp/OutputVisitor/CSharpOutputVisitor.cs b/ICSharpCode.Decompiler/CSharp/OutputVisitor/CSharpOutputVisitor.cs
index 96d591dab..a462ccb9f 100644
--- a/ICSharpCode.Decompiler/CSharp/OutputVisitor/CSharpOutputVisitor.cs
+++ b/ICSharpCode.Decompiler/CSharp/OutputVisitor/CSharpOutputVisitor.cs
@@ -1530,8 +1530,8 @@ namespace ICSharpCode.Decompiler.CSharp.OutputVisitor
 			WriteToken(Roles.RBracket);
 			switch (attributeSection.Parent)
 			{
-				case ParameterDeclaration _:
-					if (attributeSection.NextSibling is AttributeSection)
+				case ParameterDeclaration pd:
+					if (pd.Attributes.Last() != attributeSection)
 						Space(policy.SpaceBetweenParameterAttributeSections);
 					else
 						Space();
diff --git a/ICSharpCode.Decompiler/CSharp/Transforms/EscapeInvalidIdentifiers.cs b/ICSharpCode.Decompiler/CSharp/Transforms/EscapeInvalidIdentifiers.cs
index 828d220af..907fb807f 100644
--- a/ICSharpCode.Decompiler/CSharp/Transforms/EscapeInvalidIdentifiers.cs
+++ b/ICSharpCode.Decompiler/CSharp/Transforms/EscapeInvalidIdentifiers.cs
@@ -172,13 +172,27 @@ namespace ICSharpCode.Decompiler.CSharp.Transforms
 			"Microsoft.CodeAnalysis.EmbeddedAttribute",
 		};
 
+		internal static readonly HashSet<string> nonEmbeddedAttributeNames = new HashSet<string>() {
+			// non-embedded attributes, but we still want to remove them
+			"System.Runtime.CompilerServices.CompilerFeatureRequiredAttribute",
+			"System.Runtime.CompilerServices.RequiredMemberAttribute",
+			"System.Runtime.CompilerServices.IsExternalInit",
+		};
+
 		public override void VisitTypeDeclaration(TypeDeclaration typeDeclaration)
 		{
 			var typeDefinition = typeDeclaration.GetSymbol() as ITypeDefinition;
-			if (typeDefinition == null || !attributeNames.Contains(typeDefinition.FullName))
+			if (typeDefinition == null)
 				return;
-			if (!typeDefinition.HasAttribute(KnownAttribute.Embedded))
+
+			if (attributeNames.Contains(typeDefinition.FullName))
+			{
+				if (!typeDefinition.HasAttribute(KnownAttribute.Embedded))
+					return;
+			}
+			else if (!nonEmbeddedAttributeNames.Contains(typeDefinition.FullName))
 				return;
+
 			if (typeDeclaration.Parent is NamespaceDeclaration ns && ns.Members.Count == 1)
 				ns.Remove();
 			else

From 906d248403a454843e8bb397b612d5e8f0b26b0e Mon Sep 17 00:00:00 2001
From: Siegfried Pammer <siegfriedpammer@gmail.com>
Date: Wed, 12 Feb 2025 22:54:35 +0100
Subject: [PATCH 04/49] Fix #3392: uses of init-setters must use
 object-initializer syntax.

---
 .../TestCases/Pretty/InitializerTests.cs      | 24 +++++++++++++++++++
 ...ransformCollectionAndObjectInitializers.cs |  8 ++++---
 2 files changed, 29 insertions(+), 3 deletions(-)

diff --git a/ICSharpCode.Decompiler.Tests/TestCases/Pretty/InitializerTests.cs b/ICSharpCode.Decompiler.Tests/TestCases/Pretty/InitializerTests.cs
index 9accce88a..874abd89b 100644
--- a/ICSharpCode.Decompiler.Tests/TestCases/Pretty/InitializerTests.cs
+++ b/ICSharpCode.Decompiler.Tests/TestCases/Pretty/InitializerTests.cs
@@ -235,6 +235,19 @@ namespace ICSharpCode.Decompiler.Tests.TestCases.Pretty.InitializerTests
 		{
 			int Property { get; set; }
 		}
+
+#if CS90
+		public class Issue3392Type
+		{
+			public bool Flag { get; init; }
+			public List<int> List { get; } = new List<int>();
+
+			public Issue3392Type(object x)
+			{
+
+			}
+		}
+#endif
 		#endregion
 
 		private S s1;
@@ -1010,6 +1023,17 @@ namespace ICSharpCode.Decompiler.Tests.TestCases.Pretty.InitializerTests
 			otherItem.Data2.Nullable = 3m;
 			return otherItem;
 		}
+
+#if CS90
+		public Issue3392Type Issue3392(Issue3392Type x)
+		{
+			x = new Issue3392Type(null) {
+				Flag = false
+			};
+			x.List.AddRange(Enumerable.Range(0, 10));
+			return x;
+		}
+#endif
 #if CS60
 		public OtherItem2 Issue1345c()
 		{
diff --git a/ICSharpCode.Decompiler/IL/Transforms/TransformCollectionAndObjectInitializers.cs b/ICSharpCode.Decompiler/IL/Transforms/TransformCollectionAndObjectInitializers.cs
index 781a83c01..764cbd64b 100644
--- a/ICSharpCode.Decompiler/IL/Transforms/TransformCollectionAndObjectInitializers.cs
+++ b/ICSharpCode.Decompiler/IL/Transforms/TransformCollectionAndObjectInitializers.cs
@@ -103,6 +103,7 @@ namespace ICSharpCode.Decompiler.IL.Transforms
 					return;
 			}
 			int initializerItemsCount = 0;
+			bool initializerContainsInitOnlyItems = false;
 			possibleIndexVariables.Clear();
 			currentPath.Clear();
 			isCollection = false;
@@ -113,13 +114,13 @@ namespace ICSharpCode.Decompiler.IL.Transforms
 			// if the method is a setter we're dealing with an object initializer
 			// if the method is named Add and has at least 2 arguments we're dealing with a collection/dictionary initializer
 			while (pos + initializerItemsCount + 1 < block.Instructions.Count
-				&& IsPartOfInitializer(block.Instructions, pos + initializerItemsCount + 1, v, instType, ref blockKind, context))
+				&& IsPartOfInitializer(block.Instructions, pos + initializerItemsCount + 1, v, instType, ref blockKind, ref initializerContainsInitOnlyItems, context))
 			{
 				initializerItemsCount++;
 			}
 			// Do not convert the statements into an initializer if there's an incompatible usage of the initializer variable
 			// directly after the possible initializer.
-			if (IsMethodCallOnVariable(block.Instructions[pos + initializerItemsCount + 1], v))
+			if (!initializerContainsInitOnlyItems && IsMethodCallOnVariable(block.Instructions[pos + initializerItemsCount + 1], v))
 				return;
 			// Calculate the correct number of statements inside the initializer:
 			// All index variables that were used in the initializer have Index set to -1.
@@ -200,7 +201,7 @@ namespace ICSharpCode.Decompiler.IL.Transforms
 		bool isCollection;
 		readonly Stack<HashSet<AccessPathElement>> pathStack = new Stack<HashSet<AccessPathElement>>();
 
-		bool IsPartOfInitializer(InstructionCollection<ILInstruction> instructions, int pos, ILVariable target, IType rootType, ref BlockKind blockKind, StatementTransformContext context)
+		bool IsPartOfInitializer(InstructionCollection<ILInstruction> instructions, int pos, ILVariable target, IType rootType, ref BlockKind blockKind, ref bool initializerContainsInitOnlyItems, StatementTransformContext context)
 		{
 			// Include any stores to local variables that are single-assigned and do not reference the initializer-variable
 			// in the list of possible index variables.
@@ -255,6 +256,7 @@ namespace ICSharpCode.Decompiler.IL.Transforms
 						return false;
 					if (blockKind != BlockKind.ObjectInitializer && blockKind != BlockKind.WithInitializer)
 						blockKind = BlockKind.ObjectInitializer;
+					initializerContainsInitOnlyItems |= lastElement.Member is IProperty { Setter.IsInitOnly: true };
 					return true;
 				default:
 					return false;

From d7d0f8241833baab1b5417ef2462ef4dd5cdcb43 Mon Sep 17 00:00:00 2001
From: Siegfried Pammer <siegfriedpammer@gmail.com>
Date: Wed, 12 Feb 2025 22:56:49 +0100
Subject: [PATCH 05/49] Fix #3384: add special case for null-literal
 conversions in optional argument handling.

---
 .../TestCases/Pretty/OptionalArguments.cs     | 22 +++++++++++++++++++
 ICSharpCode.Decompiler/CSharp/CallBuilder.cs  |  5 ++++-
 2 files changed, 26 insertions(+), 1 deletion(-)

diff --git a/ICSharpCode.Decompiler.Tests/TestCases/Pretty/OptionalArguments.cs b/ICSharpCode.Decompiler.Tests/TestCases/Pretty/OptionalArguments.cs
index 82cda462b..d69c8f35a 100644
--- a/ICSharpCode.Decompiler.Tests/TestCases/Pretty/OptionalArguments.cs
+++ b/ICSharpCode.Decompiler.Tests/TestCases/Pretty/OptionalArguments.cs
@@ -32,6 +32,28 @@ namespace ICSharpCode.Decompiler.Tests.TestCases.Pretty
 			B
 		}
 
+		internal class OptionalArgumentTest
+		{
+			private static void Test()
+			{
+				Test2();
+				Test3();
+				Test4();
+			}
+
+			private static void Test2(int a = 0)
+			{
+			}
+
+			private static void Test3(int a = 0, int? b = null)
+			{
+			}
+
+			private static void Test4(int? b = null, int a = 0)
+			{
+			}
+		}
+
 		public OptionalArguments(string name, int a = 5)
 		{
 
diff --git a/ICSharpCode.Decompiler/CSharp/CallBuilder.cs b/ICSharpCode.Decompiler/CSharp/CallBuilder.cs
index fb46b12ea..5a5fe7802 100644
--- a/ICSharpCode.Decompiler/CSharp/CallBuilder.cs
+++ b/ICSharpCode.Decompiler/CSharp/CallBuilder.cs
@@ -1043,7 +1043,10 @@ namespace ICSharpCode.Decompiler.CSharp
 
 		bool IsOptionalArgument(IParameter parameter, TranslatedExpression arg)
 		{
-			if (!parameter.IsOptional || !arg.ResolveResult.IsCompileTimeConstant)
+			if (!parameter.IsOptional)
+				return false;
+
+			if (!arg.ResolveResult.IsCompileTimeConstant && arg.ResolveResult is not ConversionResolveResult { Conversion.IsNullLiteralConversion: true })
 				return false;
 			if (parameter.GetAttributes().Any(a => a.AttributeType.IsKnownType(KnownAttribute.CallerMemberName)
 				|| a.AttributeType.IsKnownType(KnownAttribute.CallerFilePath)

From 2c419f68cf92bebdf2c656571881efbd959de57e Mon Sep 17 00:00:00 2001
From: Siegfried Pammer <siegfriedpammer@gmail.com>
Date: Mon, 13 Jan 2025 20:04:58 +0100
Subject: [PATCH 06/49] Fix #3322: Add missing checks for equality comparison

---
 .../ICSharpCode.Decompiler.Tests.csproj        |  1 +
 .../PrettyTestRunner.cs                        |  6 ++++++
 .../TestCases/Pretty/Comparisons.cs            | 18 ++++++++++++++++++
 .../CSharp/Resolver/CSharpResolver.cs          |  5 ++++-
 4 files changed, 29 insertions(+), 1 deletion(-)
 create mode 100644 ICSharpCode.Decompiler.Tests/TestCases/Pretty/Comparisons.cs

diff --git a/ICSharpCode.Decompiler.Tests/ICSharpCode.Decompiler.Tests.csproj b/ICSharpCode.Decompiler.Tests/ICSharpCode.Decompiler.Tests.csproj
index 0ec42ecbe..3ff44e2bb 100644
--- a/ICSharpCode.Decompiler.Tests/ICSharpCode.Decompiler.Tests.csproj
+++ b/ICSharpCode.Decompiler.Tests/ICSharpCode.Decompiler.Tests.csproj
@@ -130,6 +130,7 @@
     <Compile Include="ProjectDecompiler\TargetFrameworkTests.cs" />
     <Compile Include="TestAssemblyResolver.cs" />
     <Compile Include="TestCases\ILPretty\MonoFixed.cs" />
+    <Compile Include="TestCases\Pretty\Comparisons.cs" />
     <None Include="TestCases\VBPretty\VBAutomaticEvents.vb" />
     <Compile Include="TestCases\VBPretty\VBAutomaticEvents.cs" />
     <Compile Include="TestCases\VBPretty\VBNonGenericForEach.cs" />
diff --git a/ICSharpCode.Decompiler.Tests/PrettyTestRunner.cs b/ICSharpCode.Decompiler.Tests/PrettyTestRunner.cs
index 284b51401..f4ff7c914 100644
--- a/ICSharpCode.Decompiler.Tests/PrettyTestRunner.cs
+++ b/ICSharpCode.Decompiler.Tests/PrettyTestRunner.cs
@@ -586,6 +586,12 @@ namespace ICSharpCode.Decompiler.Tests
 			await RunForLibrary(cscOptions: cscOptions);
 		}
 
+		[Test]
+		public async Task Comparisons([ValueSource(nameof(defaultOptions))] CompilerOptions cscOptions)
+		{
+			await RunForLibrary(cscOptions: cscOptions);
+		}
+
 		[Test]
 		public async Task ConstantsTests([ValueSource(nameof(defaultOptions))] CompilerOptions cscOptions)
 		{
diff --git a/ICSharpCode.Decompiler.Tests/TestCases/Pretty/Comparisons.cs b/ICSharpCode.Decompiler.Tests/TestCases/Pretty/Comparisons.cs
new file mode 100644
index 000000000..5a1df3c9b
--- /dev/null
+++ b/ICSharpCode.Decompiler.Tests/TestCases/Pretty/Comparisons.cs
@@ -0,0 +1,18 @@
+namespace ICSharpCode.Decompiler.Tests.TestCases.Pretty
+{
+	public class Comparisons
+	{
+		private class A
+		{
+		}
+
+		private class B
+		{
+		}
+
+		private bool CompareUnrelatedNeedsCast(A a, B b)
+		{
+			return (object)a == b;
+		}
+	}
+}
diff --git a/ICSharpCode.Decompiler/CSharp/Resolver/CSharpResolver.cs b/ICSharpCode.Decompiler/CSharp/Resolver/CSharpResolver.cs
index 14ab787ba..9e556e6cd 100644
--- a/ICSharpCode.Decompiler/CSharp/Resolver/CSharpResolver.cs
+++ b/ICSharpCode.Decompiler/CSharp/Resolver/CSharpResolver.cs
@@ -805,7 +805,10 @@ namespace ICSharpCode.Decompiler.CSharp.Resolver
 					}
 					if (op == BinaryOperatorType.Equality || op == BinaryOperatorType.InEquality)
 					{
-						if (lhsType.IsReferenceType == true && rhsType.IsReferenceType == true)
+						if (lhsType.IsReferenceType == true && rhsType.IsReferenceType == true
+							&& (conversions.IdentityConversion(lhsType, rhsType)
+								|| conversions.ExplicitConversion(lhsType, rhsType).IsReferenceConversion
+								|| conversions.ExplicitConversion(rhsType, lhsType).IsReferenceConversion))
 						{
 							// If it's a reference comparison
 							if (op == BinaryOperatorType.Equality)

From fb2561ac83f20060e7c374ef760562b2c3dcec7e Mon Sep 17 00:00:00 2001
From: tom-englert <mail@tom-englert.de>
Date: Wed, 19 Feb 2025 18:29:49 +0100
Subject: [PATCH 07/49] Fix #3402: System.NullReferenceException on right click
 on .NET 9 (#3403)

* Fix #3402: System.NullReferenceException on right click on .NET 9

* Add comments to clarify about used design pattern
---
 Directory.Packages.props                | 10 +++++-----
 ILSpy/AssemblyTree/AssemblyTreeModel.cs |  2 ++
 2 files changed, 7 insertions(+), 5 deletions(-)

diff --git a/Directory.Packages.props b/Directory.Packages.props
index a84164d03..f9cbde7a7 100644
--- a/Directory.Packages.props
+++ b/Directory.Packages.props
@@ -27,7 +27,7 @@
     <PackageVersion Include="Microsoft.NET.Test.Sdk" Version="17.12.0" />
     <PackageVersion Include="Microsoft.NETCore.ILAsm" Version="9.0.0" />
     <PackageVersion Include="Microsoft.NETCore.ILDAsm" Version="9.0.0" />
-	<PackageVersion Include="Microsoft.Sbom.Targets" Version="3.0.1" />
+    <PackageVersion Include="Microsoft.Sbom.Targets" Version="3.0.1" />
     <PackageVersion Include="Microsoft.SourceLink.GitHub" Version="8.0.0" />
     <PackageVersion Include="Microsoft.Xaml.Behaviors.Wpf" Version="1.1.135" />
     <PackageVersion Include="Mono.Cecil" Version="0.11.6" />
@@ -45,10 +45,10 @@
     <PackageVersion Include="System.Reflection.Metadata" Version="9.0.1" />
     <PackageVersion Include="System.Resources.Extensions" Version="9.0.1" />
     <PackageVersion Include="System.Runtime.CompilerServices.Unsafe" Version="6.1.0" />
-    <PackageVersion Include="TomsToolbox.Composition.MicrosoftExtensions" Version="2.21.0" />
-    <PackageVersion Include="TomsToolbox.Wpf.Composition" Version="2.20.0" />
-    <PackageVersion Include="TomsToolbox.Wpf.Composition.AttributedModel" Version="2.21.0" />
-    <PackageVersion Include="TomsToolbox.Wpf.Styles" Version="2.21.0" />
+    <PackageVersion Include="TomsToolbox.Composition.MicrosoftExtensions" Version="2.22.0" />
+    <PackageVersion Include="TomsToolbox.Wpf.Composition" Version="2.22.0" />
+    <PackageVersion Include="TomsToolbox.Wpf.Composition.AttributedModel" Version="2.22.0" />
+    <PackageVersion Include="TomsToolbox.Wpf.Styles" Version="2.22.0" />
     <PackageVersion Include="coverlet.collector" Version="6.0.3" />
     <PackageVersion Include="System.Net.Http" Version="4.3.4" />
     <PackageVersion Include="System.Private.Uri" Version="4.3.2" />
diff --git a/ILSpy/AssemblyTree/AssemblyTreeModel.cs b/ILSpy/AssemblyTree/AssemblyTreeModel.cs
index 11df01390..dd5ba6fba 100644
--- a/ILSpy/AssemblyTree/AssemblyTreeModel.cs
+++ b/ILSpy/AssemblyTree/AssemblyTreeModel.cs
@@ -767,6 +767,8 @@ namespace ICSharpCode.ILSpy.AssemblyTree
 				}
 				else
 				{
+					// ensure that we are only connected once to the event, else we might get multiple notifications
+					ContextMenuProvider.ContextMenuClosed -= ContextMenuClosed;
 					ContextMenuProvider.ContextMenuClosed += ContextMenuClosed;
 				}
 			}

From 202c5e22e314be52dcf90894b387dad7aa4c0b89 Mon Sep 17 00:00:00 2001
From: Siegfried Pammer <siegfriedpammer@gmail.com>
Date: Sun, 16 Feb 2025 09:12:00 +0100
Subject: [PATCH 08/49] Fix #3383: more aggressively transform object
 initializers on structs

---
 .../TestCases/Correctness/InitializerTests.cs | 16 -----
 .../TestCases/Pretty/InitializerTests.cs      | 61 ++++++++++++++-----
 ...ransformCollectionAndObjectInitializers.cs |  8 ---
 3 files changed, 45 insertions(+), 40 deletions(-)

diff --git a/ICSharpCode.Decompiler.Tests/TestCases/Correctness/InitializerTests.cs b/ICSharpCode.Decompiler.Tests/TestCases/Correctness/InitializerTests.cs
index 1e0bd901b..7d4653f29 100644
--- a/ICSharpCode.Decompiler.Tests/TestCases/Correctness/InitializerTests.cs
+++ b/ICSharpCode.Decompiler.Tests/TestCases/Correctness/InitializerTests.cs
@@ -318,14 +318,6 @@ namespace ICSharpCode.Decompiler.Tests.TestCases.Correctness
 			};
 		}
 
-		public static void NotAStructInitializer_DefaultConstructor()
-		{
-			InitializerTests.StructData data = new InitializerTests.StructData();
-			data.Field = 1;
-			data.Property = 2;
-			InitializerTests.X(InitializerTests.Y(), data);
-		}
-
 		public static void StructInitializer_DefaultConstructor()
 		{
 			InitializerTests.X(InitializerTests.Y(), new InitializerTests.StructData {
@@ -334,14 +326,6 @@ namespace ICSharpCode.Decompiler.Tests.TestCases.Correctness
 			});
 		}
 
-		public static void NotAStructInitializer_ExplicitConstructor()
-		{
-			InitializerTests.StructData data = new InitializerTests.StructData(0);
-			data.Field = 1;
-			data.Property = 2;
-			InitializerTests.X(InitializerTests.Y(), data);
-		}
-
 		public static void StructInitializer_ExplicitConstructor()
 		{
 			InitializerTests.X(InitializerTests.Y(), new InitializerTests.StructData(0) {
diff --git a/ICSharpCode.Decompiler.Tests/TestCases/Pretty/InitializerTests.cs b/ICSharpCode.Decompiler.Tests/TestCases/Pretty/InitializerTests.cs
index 874abd89b..e02f23ff9 100644
--- a/ICSharpCode.Decompiler.Tests/TestCases/Pretty/InitializerTests.cs
+++ b/ICSharpCode.Decompiler.Tests/TestCases/Pretty/InitializerTests.cs
@@ -247,6 +247,51 @@ namespace ICSharpCode.Decompiler.Tests.TestCases.Pretty.InitializerTests
 
 			}
 		}
+
+		private class StructInitPropertiesTest
+		{
+			private class TypeA
+			{
+				public int A { get; set; }
+				public int B { get; set; }
+			}
+
+			private struct TypeB
+			{
+				public int A { get; set; }
+				public int B { get; set; }
+			}
+
+			private struct TypeC
+			{
+				public int A { get; init; }
+				public int B { get; init; }
+			}
+
+			private static TypeA TestA()
+			{
+				return new TypeA {
+					A = 1,
+					B = 2
+				};
+			}
+
+			private static TypeB TestB()
+			{
+				return new TypeB {
+					A = 1,
+					B = 2
+				};
+			}
+
+			private static TypeC TestC()
+			{
+				return new TypeC {
+					A = 1,
+					B = 2
+				};
+			}
+		}
 #endif
 		#endregion
 
@@ -928,14 +973,6 @@ namespace ICSharpCode.Decompiler.Tests.TestCases.Pretty.InitializerTests
 			});
 		}
 
-		public static void NotAStructInitializer_DefaultConstructor()
-		{
-			StructData structData = default(StructData);
-			structData.Field = 1;
-			structData.Property = 2;
-			X(Y(), structData);
-		}
-
 		public static void StructInitializer_DefaultConstructor()
 		{
 			X(Y(), new StructData {
@@ -956,14 +993,6 @@ namespace ICSharpCode.Decompiler.Tests.TestCases.Pretty.InitializerTests
 			};
 		}
 
-		public static void NotAStructInitializer_ExplicitConstructor()
-		{
-			StructData structData = new StructData(0);
-			structData.Field = 1;
-			structData.Property = 2;
-			X(Y(), structData);
-		}
-
 		public static void StructInitializer_ExplicitConstructor()
 		{
 			X(Y(), new StructData(0) {
diff --git a/ICSharpCode.Decompiler/IL/Transforms/TransformCollectionAndObjectInitializers.cs b/ICSharpCode.Decompiler/IL/Transforms/TransformCollectionAndObjectInitializers.cs
index 764cbd64b..c632e22a2 100644
--- a/ICSharpCode.Decompiler/IL/Transforms/TransformCollectionAndObjectInitializers.cs
+++ b/ICSharpCode.Decompiler/IL/Transforms/TransformCollectionAndObjectInitializers.cs
@@ -73,14 +73,6 @@ namespace ICSharpCode.Decompiler.IL.Transforms
 					instType = newObjInst.Method.DeclaringType;
 					break;
 				case DefaultValue defaultVal:
-					if (defaultVal.ILStackWasEmpty && v.Kind == VariableKind.Local && !currentMethod.IsConstructor)
-					{
-						// on statement level (no other expressions on IL stack),
-						// prefer to keep local variables (but not stack slots),
-						// unless we are in a constructor (where inlining object initializers might be 
-						// critical for the base ctor call)
-						return;
-					}
 					instType = defaultVal.Type;
 					break;
 				case Call c when c.Method.FullNameIs("System.Activator", "CreateInstance") && c.Method.TypeArguments.Count == 1:

From 00969946795b5d36caebe75dc56ee25672e1f749 Mon Sep 17 00:00:00 2001
From: Siegfried Pammer <siegfriedpammer@gmail.com>
Date: Thu, 20 Feb 2025 10:00:59 +0100
Subject: [PATCH 09/49] Fix #3401: normalize directory separators to use the
 current platform separator

---
 .../CSharp/ProjectDecompiler/WholeProjectDecompiler.cs        | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)

diff --git a/ICSharpCode.Decompiler/CSharp/ProjectDecompiler/WholeProjectDecompiler.cs b/ICSharpCode.Decompiler/CSharp/ProjectDecompiler/WholeProjectDecompiler.cs
index f8b687b73..da5671559 100644
--- a/ICSharpCode.Decompiler/CSharp/ProjectDecompiler/WholeProjectDecompiler.cs
+++ b/ICSharpCode.Decompiler/CSharp/ProjectDecompiler/WholeProjectDecompiler.cs
@@ -692,10 +692,10 @@ namespace ICSharpCode.Decompiler.CSharp.ProjectDecompiler
 					if (separateAtDots)
 						currentSegmentLength = 0;
 				}
-				else if (treatAsFileName && (c == '/' || c == '\\') && currentSegmentLength > 1)
+				else if (treatAsFileName && (c is '/' or '\\') && currentSegmentLength > 1)
 				{
 					// if we treat this as a file name, we've started a new segment
-					b.Append(c);
+					b.Append(Path.DirectorySeparatorChar);
 					currentSegmentLength = 0;
 				}
 				else

From 807ac32aab8705d21cca80bea0b661b9cbccff08 Mon Sep 17 00:00:00 2001
From: Siegfried Pammer <siegfriedpammer@gmail.com>
Date: Thu, 20 Feb 2025 10:29:52 +0100
Subject: [PATCH 10/49] Hide ItemsControl of ComboBox width hack, if
 languageVersionComboBox is invisible.

---
 ILSpy/Controls/MainToolBar.xaml | 1 +
 1 file changed, 1 insertion(+)

diff --git a/ILSpy/Controls/MainToolBar.xaml b/ILSpy/Controls/MainToolBar.xaml
index 867c8119d..927747f31 100644
--- a/ILSpy/Controls/MainToolBar.xaml
+++ b/ILSpy/Controls/MainToolBar.xaml
@@ -75,6 +75,7 @@
 		<Grid Margin="2,0">
 			<ItemsControl
 				ItemsSource="{Binding SelectedItem.LanguageVersions, ElementName=languageComboBox, UpdateSourceTrigger=PropertyChanged}"
+				Visibility="{Binding SelectedItem.HasLanguageVersions, ElementName=languageComboBox, Converter={toms:BooleanToVisibilityConverter}}"
 				DisplayMemberPath="DisplayName" Height="0" Margin="15,0" />
 			<ComboBox Name="languageVersionComboBox" DisplayMemberPath="DisplayName" MaxDropDownHeight="Auto"
 			          ToolTip="{x:Static properties:Resources.SelectVersionDropdownTooltip}"

From 5b90dbdabf654d670ae5b0dd863df6214e485a61 Mon Sep 17 00:00:00 2001
From: Siegfried Pammer <siegfriedpammer@gmail.com>
Date: Thu, 20 Feb 2025 10:49:50 +0100
Subject: [PATCH 11/49] Fix #3385: Allow address uses of structs in using
 transform, if the reference is passed to an in parameter.

---
 .../TestCases/Pretty/Using.cs                 | 46 +++++++++++++++++++
 .../IL/Transforms/ILInlining.cs               |  2 +-
 .../IL/Transforms/UsingTransform.cs           | 14 +++++-
 3 files changed, 60 insertions(+), 2 deletions(-)

diff --git a/ICSharpCode.Decompiler.Tests/TestCases/Pretty/Using.cs b/ICSharpCode.Decompiler.Tests/TestCases/Pretty/Using.cs
index 9674b8cf7..04e819c70 100644
--- a/ICSharpCode.Decompiler.Tests/TestCases/Pretty/Using.cs
+++ b/ICSharpCode.Decompiler.Tests/TestCases/Pretty/Using.cs
@@ -39,6 +39,32 @@ namespace ICSharpCode.Decompiler.Tests.TestCases.Pretty
 			}
 		}
 
+		private struct TypeA_Issue3385 : IDisposable
+		{
+			private int dummy;
+
+			public void Dispose()
+			{
+			}
+
+#if !ROSLYN3
+			public static implicit operator TypeB_Issue3385(TypeA_Issue3385 a)
+			{
+				return default(TypeB_Issue3385);
+			}
+#else
+			public static implicit operator TypeB_Issue3385(in TypeA_Issue3385 a)
+			{
+				return default(TypeB_Issue3385);
+			}
+#endif
+		}
+
+		private struct TypeB_Issue3385
+		{
+			private int dummy;
+		}
+
 #if CS80
 		[StructLayout(LayoutKind.Sequential, Size = 1)]
 		public ref struct UsingRefStruct
@@ -161,5 +187,25 @@ namespace ICSharpCode.Decompiler.Tests.TestCases.Pretty
 			}
 		}
 #endif
+
+		public static void Issue3385()
+		{
+#if ROSLYN3
+			using (TypeA_Issue3385 a = default(TypeA_Issue3385))
+#else
+			using (TypeA_Issue3385 typeA_Issue = default(TypeA_Issue3385))
+#endif
+			{
+#if ROSLYN3
+				Empty(a);
+#else
+				Empty(typeA_Issue);
+#endif
+			}
+		}
+
+		private static void Empty(TypeB_Issue3385 b)
+		{
+		}
 	}
 }
diff --git a/ICSharpCode.Decompiler/IL/Transforms/ILInlining.cs b/ICSharpCode.Decompiler/IL/Transforms/ILInlining.cs
index 7b38b2e90..3e2c743c8 100644
--- a/ICSharpCode.Decompiler/IL/Transforms/ILInlining.cs
+++ b/ICSharpCode.Decompiler/IL/Transforms/ILInlining.cs
@@ -448,7 +448,7 @@ namespace ICSharpCode.Decompiler.IL.Transforms
 			return inst != ldloca && inst.Parent is LdObj;
 		}
 
-		static bool IsPassedToInParameter(LdLoca ldloca)
+		internal static bool IsPassedToInParameter(LdLoca ldloca)
 		{
 			if (ldloca.Parent is not CallInstruction call)
 			{
diff --git a/ICSharpCode.Decompiler/IL/Transforms/UsingTransform.cs b/ICSharpCode.Decompiler/IL/Transforms/UsingTransform.cs
index 96fe3d13d..020166ac0 100644
--- a/ICSharpCode.Decompiler/IL/Transforms/UsingTransform.cs
+++ b/ICSharpCode.Decompiler/IL/Transforms/UsingTransform.cs
@@ -89,7 +89,7 @@ namespace ICSharpCode.Decompiler.IL.Transforms
 				return false;
 			if (storeInst.Variable.LoadInstructions.Any(ld => !ld.IsDescendantOf(tryFinally)))
 				return false;
-			if (storeInst.Variable.AddressInstructions.Any(la => !la.IsDescendantOf(tryFinally) || (la.IsDescendantOf(tryFinally.TryBlock) && !ILInlining.IsUsedAsThisPointerInCall(la))))
+			if (!storeInst.Variable.AddressInstructions.All(ValidateAddressUse))
 				return false;
 			if (storeInst.Variable.StoreInstructions.Count > 1)
 				return false;
@@ -104,6 +104,18 @@ namespace ICSharpCode.Decompiler.IL.Transforms
 				IsRefStruct = context.Settings.IntroduceRefModifiersOnStructs && storeInst.Variable.Type.Kind == TypeKind.Struct && storeInst.Variable.Type.IsByRefLike
 			}.WithILRange(storeInst);
 			return true;
+
+			bool ValidateAddressUse(LdLoca la)
+			{
+				if (!la.IsDescendantOf(tryFinally))
+					return false;
+				if (la.IsDescendantOf(tryFinally.TryBlock))
+				{
+					if (!(ILInlining.IsUsedAsThisPointerInCall(la) || ILInlining.IsPassedToInParameter(la)))
+						return false;
+				}
+				return true;
+			}
 		}
 
 		/// <summary>

From c0c5559a7b87d7322ec5228bd3da2cf3136ef7c9 Mon Sep 17 00:00:00 2001
From: Siegfried Pammer <siegfriedpammer@gmail.com>
Date: Thu, 20 Feb 2025 11:53:59 +0100
Subject: [PATCH 12/49] Fix #3399: Add module filename as ToolTip of
 AnalyzerEntityTreeNode

---
 ILSpy/Analyzers/AnalyzerEntityTreeNode.cs | 4 +++-
 1 file changed, 3 insertions(+), 1 deletion(-)

diff --git a/ILSpy/Analyzers/AnalyzerEntityTreeNode.cs b/ILSpy/Analyzers/AnalyzerEntityTreeNode.cs
index 4217e411c..630bde183 100644
--- a/ILSpy/Analyzers/AnalyzerEntityTreeNode.cs
+++ b/ILSpy/Analyzers/AnalyzerEntityTreeNode.cs
@@ -22,8 +22,8 @@ using System.Windows;
 using ICSharpCode.Decompiler.TypeSystem;
 using ICSharpCode.ILSpy.TreeNodes;
 using ICSharpCode.ILSpyX;
-using ICSharpCode.ILSpyX.TreeView.PlatformAbstractions;
 using ICSharpCode.ILSpyX.TreeView;
+using ICSharpCode.ILSpyX.TreeView.PlatformAbstractions;
 
 namespace ICSharpCode.ILSpy.Analyzers
 {
@@ -46,6 +46,8 @@ namespace ICSharpCode.ILSpy.Analyzers
 			MessageBus.Send(this, new NavigateToReferenceEventArgs(new EntityReference(this.Member.ParentModule?.MetadataFile, this.Member.MetadataToken)));
 		}
 
+		public override object ToolTip => Member.ParentModule?.MetadataFile?.FileName;
+
 		public override bool HandleAssemblyListChanged(ICollection<LoadedAssembly> removedAssemblies, ICollection<LoadedAssembly> addedAssemblies)
 		{
 			foreach (LoadedAssembly asm in removedAssemblies)

From 79ddc448b6139912613cfddce66908b07c919137 Mon Sep 17 00:00:00 2001
From: Siegfried Pammer <siegfriedpammer@gmail.com>
Date: Thu, 20 Feb 2025 14:12:03 +0100
Subject: [PATCH 13/49] Fix #3377: Extend error information if multiple
 assemblies with the same (short) name are selected when creating a solution
 file.

---
 ILSpy/SolutionWriter.cs | 38 +++++++++++++++++++++++++++++++++-----
 1 file changed, 33 insertions(+), 5 deletions(-)

diff --git a/ILSpy/SolutionWriter.cs b/ILSpy/SolutionWriter.cs
index 0fee4c18a..29e8db8b0 100644
--- a/ILSpy/SolutionWriter.cs
+++ b/ILSpy/SolutionWriter.cs
@@ -28,7 +28,6 @@ using System.Threading.Tasks;
 using ICSharpCode.Decompiler;
 using ICSharpCode.Decompiler.Solution;
 using ICSharpCode.Decompiler.Util;
-using ICSharpCode.ILSpy.Docking;
 using ICSharpCode.ILSpy.TextView;
 using ICSharpCode.ILSpy.ViewModels;
 using ICSharpCode.ILSpyX;
@@ -99,13 +98,42 @@ namespace ICSharpCode.ILSpy
 		{
 			var result = new AvalonEditTextOutput();
 
-			var duplicates = new HashSet<string>();
-			if (assemblies.Any(asm => !duplicates.Add(asm.ShortName)))
+			var assembliesByShortName = assemblies.ToLookup(_ => _.ShortName);
+			bool first = true;
+			bool abort = false;
+
+			foreach (var item in assembliesByShortName)
 			{
-				result.WriteLine("Duplicate assembly names selected, cannot generate a solution.");
-				return result;
+				var enumerator = item.GetEnumerator();
+				if (!enumerator.MoveNext())
+					continue;
+				var firstAssembly = enumerator.Current;
+				if (!enumerator.MoveNext())
+					continue;
+				if (first)
+				{
+					result.WriteLine("Duplicate assembly names selected, cannot generate a solution:");
+					abort = true;
+				}
+
+				result.Write("- " + firstAssembly.Text + " conflicts with ");
+
+				first = true;
+				do
+				{
+					var asm = enumerator.Current;
+					if (!first)
+						result.Write(", ");
+					result.Write(asm.Text);
+					first = false;
+				} while (enumerator.MoveNext());
+				result.WriteLine();
+				first = false;
 			}
 
+			if (abort)
+				return result;
+
 			Stopwatch stopwatch = Stopwatch.StartNew();
 
 			try

From 8eadd907c9bcb92e89f23d31d2b8f4fab8dc21e4 Mon Sep 17 00:00:00 2001
From: tom-englert <mail@tom-englert.de>
Date: Thu, 20 Feb 2025 16:19:26 +0100
Subject: [PATCH 14/49] Fix #3393: Option to turn off smooth scrolling

---
 ILSpy/App.xaml                          | 12 ++++++++++--
 ILSpy/App.xaml.cs                       |  2 ++
 ILSpy/Controls/ZoomScrollViewer.xaml    | 12 ++++++++++--
 ILSpy/Options/DisplaySettings.cs        |  8 ++++++++
 ILSpy/Options/DisplaySettingsPanel.xaml |  1 +
 ILSpy/Properties/Resources.Designer.cs  |  9 +++++++++
 ILSpy/Properties/Resources.resx         |  3 +++
 7 files changed, 43 insertions(+), 4 deletions(-)

diff --git a/ILSpy/App.xaml b/ILSpy/App.xaml
index ce2e14086..aac1e7181 100644
--- a/ILSpy/App.xaml
+++ b/ILSpy/App.xaml
@@ -3,7 +3,9 @@
 			 xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
 			 xmlns:styles="urn:TomsToolbox.Wpf.Styles"
 			 xmlns:toms="urn:TomsToolbox"
-			 xmlns:themes="clr-namespace:ICSharpCode.ILSpy.Themes">
+			 xmlns:themes="clr-namespace:ICSharpCode.ILSpy.Themes"
+			 xmlns:composition="urn:TomsToolbox.Composition"
+			 xmlns:util="clr-namespace:ICSharpCode.ILSpy.Util">
 	<Application.Resources>
 		<Style x:Key="DialogWindow" TargetType="{x:Type Window}">
 			<Setter Property="ShowInTaskbar" Value="False" />
@@ -24,7 +26,13 @@
 		</Style>
 
 		<Style TargetType="ScrollViewer">
-			<Setter Property="toms:AdvancedScrollWheelBehavior.Attach" Value="WithAnimation" />
+			<Setter Property="toms:StyleBindings.Behaviors">
+				<Setter.Value>
+					<toms:BehaviorCollection>
+						<toms:AdvancedScrollWheelBehavior UseScrollingAnimation="{Binding Path=(util:SettingsService.DisplaySettings).EnableSmoothScrolling, Source={composition:Import util:SettingsService}}"/>
+					</toms:BehaviorCollection>
+				</Setter.Value>
+			</Setter>
 		</Style>
 
 	</Application.Resources>
diff --git a/ILSpy/App.xaml.cs b/ILSpy/App.xaml.cs
index c82af3125..432499935 100644
--- a/ILSpy/App.xaml.cs
+++ b/ILSpy/App.xaml.cs
@@ -69,6 +69,8 @@ namespace ICSharpCode.ILSpy
 			var cmdArgs = Environment.GetCommandLineArgs().Skip(1);
 			CommandLineArguments = CommandLineArguments.Create(cmdArgs);
 
+			// This is only a temporary, read only handle to the settings service to access the AllowMultipleInstances setting before DI is initialized.
+			// At runtime, you must use the service via DI!
 			var settingsService = new SettingsService();
 
 			bool forceSingleInstance = (CommandLineArguments.SingleInstance ?? true)
diff --git a/ILSpy/Controls/ZoomScrollViewer.xaml b/ILSpy/Controls/ZoomScrollViewer.xaml
index 509b8d636..648e1fb42 100644
--- a/ILSpy/Controls/ZoomScrollViewer.xaml
+++ b/ILSpy/Controls/ZoomScrollViewer.xaml
@@ -1,10 +1,18 @@
 <ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
 	xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
 	xmlns:Controls="clr-namespace:ICSharpCode.ILSpy.Controls"
-	xmlns:toms="urn:TomsToolbox">
+	xmlns:toms="urn:TomsToolbox"
+	xmlns:composition="urn:TomsToolbox.Composition"
+	xmlns:util="clr-namespace:ICSharpCode.ILSpy.Util">
 	
 	<Style TargetType="{x:Type Controls:ZoomScrollViewer}">
-		<Setter Property="toms:AdvancedScrollWheelBehavior.Attach" Value="WithAnimation" />
+		<Setter Property="toms:StyleBindings.Behaviors">
+			<Setter.Value>
+				<toms:BehaviorCollection>
+					<toms:AdvancedScrollWheelBehavior UseScrollingAnimation="{Binding Path=(util:SettingsService.DisplaySettings).EnableSmoothScrolling, Source={composition:Import util:SettingsService}}"/>
+				</toms:BehaviorCollection>
+			</Setter.Value>
+		</Setter>
 		<Setter Property="Template">
 			<Setter.Value>
 				<ControlTemplate TargetType="{x:Type Controls:ZoomScrollViewer}">
diff --git a/ILSpy/Options/DisplaySettings.cs b/ILSpy/Options/DisplaySettings.cs
index 9eb07b394..8b21987bb 100644
--- a/ILSpy/Options/DisplaySettings.cs
+++ b/ILSpy/Options/DisplaySettings.cs
@@ -148,6 +148,12 @@ namespace ICSharpCode.ILSpy.Options
 			set => SetProperty(ref showRawOffsetsAndBytesBeforeInstruction, value);
 		}
 
+		private bool enableSmoothScrolling;
+		public bool EnableSmoothScrolling {
+			get => enableSmoothScrolling;
+			set => SetProperty(ref enableSmoothScrolling, value);
+		}
+
 		public XName SectionName => "DisplaySettings";
 
 		public void LoadFromXml(XElement section)
@@ -172,6 +178,7 @@ namespace ICSharpCode.ILSpy.Options
 			UseNestedNamespaceNodes = (bool?)section.Attribute("UseNestedNamespaceNodes") ?? false;
 			ShowRawOffsetsAndBytesBeforeInstruction = (bool?)section.Attribute("ShowRawOffsetsAndBytesBeforeInstruction") ?? false;
 			StyleWindowTitleBar = (bool?)section.Attribute("StyleWindowTitleBar") ?? false;
+			EnableSmoothScrolling = (bool?)section.Attribute("EnableSmoothScrolling") ?? true;
 		}
 
 		public XElement SaveToXml()
@@ -198,6 +205,7 @@ namespace ICSharpCode.ILSpy.Options
 			section.SetAttributeValue("UseNestedNamespaceNodes", UseNestedNamespaceNodes);
 			section.SetAttributeValue("ShowRawOffsetsAndBytesBeforeInstruction", ShowRawOffsetsAndBytesBeforeInstruction);
 			section.SetAttributeValue("StyleWindowTitleBar", StyleWindowTitleBar);
+			section.SetAttributeValue("EnableSmoothScrolling", EnableSmoothScrolling);
 
 			return section;
 		}
diff --git a/ILSpy/Options/DisplaySettingsPanel.xaml b/ILSpy/Options/DisplaySettingsPanel.xaml
index 9fb50d7a9..7df9a6b60 100644
--- a/ILSpy/Options/DisplaySettingsPanel.xaml
+++ b/ILSpy/Options/DisplaySettingsPanel.xaml
@@ -78,6 +78,7 @@
 				<StackPanel Margin="3">
 					<CheckBox IsChecked="{Binding Settings.SortResults}" Content="{x:Static properties:Resources.SortResultsFitness}"></CheckBox>
 					<CheckBox IsChecked="{Binding Settings.StyleWindowTitleBar}" Content="{x:Static properties:Resources.StyleTheWindowTitleBar}"></CheckBox>
+					<CheckBox IsChecked="{Binding Settings.EnableSmoothScrolling}" Content="{x:Static properties:Resources.EnableSmoothScrolling}"></CheckBox>
 				</StackPanel>
 			</GroupBox>
 		</StackPanel>
diff --git a/ILSpy/Properties/Resources.Designer.cs b/ILSpy/Properties/Resources.Designer.cs
index 01628056b..7e9235cae 100644
--- a/ILSpy/Properties/Resources.Designer.cs
+++ b/ILSpy/Properties/Resources.Designer.cs
@@ -1722,6 +1722,15 @@ namespace ICSharpCode.ILSpy.Properties {
             }
         }
         
+        /// <summary>
+        ///   Looks up a localized string similar to Enable smooth scrolling.
+        /// </summary>
+        public static string EnableSmoothScrolling {
+            get {
+                return ResourceManager.GetString("EnableSmoothScrolling", resourceCulture);
+            }
+        }
+        
         /// <summary>
         ///   Looks up a localized string similar to Enable word wrap.
         /// </summary>
diff --git a/ILSpy/Properties/Resources.resx b/ILSpy/Properties/Resources.resx
index bcb79fae7..a6e65ca67 100644
--- a/ILSpy/Properties/Resources.resx
+++ b/ILSpy/Properties/Resources.resx
@@ -591,6 +591,9 @@ Are you sure you want to continue?</value>
   <data name="EnableFoldingBlocksBraces" xml:space="preserve">
     <value>Enable folding on all blocks in braces</value>
   </data>
+  <data name="EnableSmoothScrolling" xml:space="preserve">
+	  <value>Enable smooth scrolling</value>
+  </data>
   <data name="EnableWordWrap" xml:space="preserve">
     <value>Enable word wrap</value>
   </data>

From 8b768794937b1ad250578384a3bed9f8bf7266d6 Mon Sep 17 00:00:00 2001
From: Siegfried Pammer <siegfriedpammer@gmail.com>
Date: Thu, 20 Feb 2025 22:23:06 +0100
Subject: [PATCH 15/49] Fix #2716: Add an option to allow sorting custom
 attributes

---
 .../CSharp/CSharpDecompiler.cs                |  1 +
 .../CSharp/Syntax/TypeSystemAstBuilder.cs     | 78 +++++++++++++++++--
 ICSharpCode.Decompiler/DecompilerSettings.cs  | 18 +++++
 .../ICSharpCode.Decompiler.csproj             |  1 +
 .../Util/DelegateComparer.cs                  | 39 ++++++++++
 ILSpy/Properties/Resources.Designer.cs        |  9 +++
 ILSpy/Properties/Resources.resx               |  3 +
 ILSpy/Properties/Resources.zh-Hans.resx       |  3 +
 8 files changed, 144 insertions(+), 8 deletions(-)
 create mode 100644 ICSharpCode.Decompiler/Util/DelegateComparer.cs

diff --git a/ICSharpCode.Decompiler/CSharp/CSharpDecompiler.cs b/ICSharpCode.Decompiler/CSharp/CSharpDecompiler.cs
index 7147785de..86f6ed66e 100644
--- a/ICSharpCode.Decompiler/CSharp/CSharpDecompiler.cs
+++ b/ICSharpCode.Decompiler/CSharp/CSharpDecompiler.cs
@@ -528,6 +528,7 @@ namespace ICSharpCode.Decompiler.CSharp
 		{
 			var typeSystemAstBuilder = new TypeSystemAstBuilder();
 			typeSystemAstBuilder.ShowAttributes = true;
+			typeSystemAstBuilder.SortAttributes = settings.SortCustomAttributes;
 			typeSystemAstBuilder.AlwaysUseShortTypeNames = true;
 			typeSystemAstBuilder.AddResolveResultAnnotations = true;
 			typeSystemAstBuilder.UseNullableSpecifierForValueTypes = settings.LiftNullables;
diff --git a/ICSharpCode.Decompiler/CSharp/Syntax/TypeSystemAstBuilder.cs b/ICSharpCode.Decompiler/CSharp/Syntax/TypeSystemAstBuilder.cs
index c26998303..f217717f1 100644
--- a/ICSharpCode.Decompiler/CSharp/Syntax/TypeSystemAstBuilder.cs
+++ b/ICSharpCode.Decompiler/CSharp/Syntax/TypeSystemAstBuilder.cs
@@ -147,6 +147,12 @@ namespace ICSharpCode.Decompiler.CSharp.Syntax
 		/// </summary>
 		public bool ShowAttributes { get; set; }
 
+		/// <summary>
+		/// Controls whether to sort attributes, if set to <see langword="false" /> attributes are shown in metadata order.
+		/// The default value is <see langword="false" />.
+		/// </summary>
+		public bool SortAttributes { get; set; }
+
 		/// <summary>
 		/// Controls whether to use fully-qualified type names or short type names.
 		/// The default value is <see langword="false" />.
@@ -793,16 +799,72 @@ namespace ICSharpCode.Decompiler.CSharp.Syntax
 			return attr;
 		}
 
-		private IEnumerable<AttributeSection> ConvertAttributes(IEnumerable<IAttribute> attributes)
-		{
-			return attributes.Select(a => new AttributeSection(ConvertAttribute(a)));
-		}
-
-		private IEnumerable<AttributeSection> ConvertAttributes(IEnumerable<IAttribute> attributes, string target)
+		private IEnumerable<AttributeSection> ConvertAttributes(IEnumerable<IAttribute> attributes, string target = null)
 		{
-			return attributes.Select(a => new AttributeSection(ConvertAttribute(a)) {
-				AttributeTarget = target
+			if (SortAttributes)
+				attributes = attributes.OrderBy(a => a, new DelegateComparer<IAttribute>(CompareAttribute));
+			return attributes.Select(a => {
+				var section = new AttributeSection(ConvertAttribute(a));
+				if (target != null)
+					section.AttributeTarget = target;
+				return section;
 			});
+
+			static int CompareAttribute(IAttribute a, IAttribute b)
+			{
+				int result = CompareType(a.AttributeType, b.AttributeType);
+				if (result != 0)
+					return result;
+				if (a.HasDecodeErrors && b.HasDecodeErrors)
+					return 0;
+				if (a.HasDecodeErrors)
+					return -1;
+				if (b.HasDecodeErrors)
+					return 1;
+				result = a.FixedArguments.Length - b.FixedArguments.Length;
+				if (result != 0)
+					return result;
+				for (int i = 0; i < a.FixedArguments.Length; i++)
+				{
+					var argA = a.FixedArguments[i];
+					var argB = b.FixedArguments[i];
+					result = CompareType(argA.Type, argB.Type);
+					if (result != 0)
+						return result;
+					if (argA.Value is IComparable compA && argB.Value is IComparable compB)
+						result = compA.CompareTo(compB);
+					else
+						result = 0;
+					if (result != 0)
+						return result;
+				}
+				result = a.NamedArguments.Length - b.NamedArguments.Length;
+				if (result != 0)
+					return result;
+				for (int i = 0; i < a.FixedArguments.Length; i++)
+				{
+					var argA = a.NamedArguments[i];
+					var argB = b.NamedArguments[i];
+					result = argA.Name.CompareTo(argB.Name);
+					if (result != 0)
+						return result;
+					result = CompareType(argA.Type, argB.Type);
+					if (result != 0)
+						return result;
+					if (argA.Value is IComparable compA && argB.Value is IComparable compB)
+						result = compA.CompareTo(compB);
+					else
+						result = 0;
+					if (result != 0)
+						return result;
+				}
+				return 0;
+			}
+
+			static int CompareType(IType a, IType b)
+			{
+				return a.FullName.CompareTo(b.FullName);
+			}
 		}
 		#endregion
 
diff --git a/ICSharpCode.Decompiler/DecompilerSettings.cs b/ICSharpCode.Decompiler/DecompilerSettings.cs
index 773128bc8..b2d7f75e2 100644
--- a/ICSharpCode.Decompiler/DecompilerSettings.cs
+++ b/ICSharpCode.Decompiler/DecompilerSettings.cs
@@ -2148,6 +2148,24 @@ namespace ICSharpCode.Decompiler
 			}
 		}
 
+		bool sortCustomAttributes = false;
+
+		/// <summary>
+		/// Sort custom attributes.
+		/// </summary>
+		[Category("DecompilerSettings.Other")]
+		[Description("DecompilerSettings.SortCustomAttributes")]
+		public bool SortCustomAttributes {
+			get { return sortCustomAttributes; }
+			set {
+				if (sortCustomAttributes != value)
+				{
+					sortCustomAttributes = value;
+					OnPropertyChanged();
+				}
+			}
+		}
+
 		CSharpFormattingOptions csharpFormattingOptions;
 
 		[Browsable(false)]
diff --git a/ICSharpCode.Decompiler/ICSharpCode.Decompiler.csproj b/ICSharpCode.Decompiler/ICSharpCode.Decompiler.csproj
index af50e7947..2a9eb220b 100644
--- a/ICSharpCode.Decompiler/ICSharpCode.Decompiler.csproj
+++ b/ICSharpCode.Decompiler/ICSharpCode.Decompiler.csproj
@@ -154,6 +154,7 @@
     <Compile Include="Properties\DecompilerVersionInfo.cs" />
     <Compile Include="TypeSystem\ITypeDefinitionOrUnknown.cs" />
     <Compile Include="Util\BitOperations.cs" />
+    <Compile Include="Util\DelegateComparer.cs" />
     <Compile Include="Util\Index.cs" />
     <Compile Include="Metadata\WebCilFile.cs" />
     <None Include="Properties\DecompilerVersionInfo.template.cs" />
diff --git a/ICSharpCode.Decompiler/Util/DelegateComparer.cs b/ICSharpCode.Decompiler/Util/DelegateComparer.cs
new file mode 100644
index 000000000..192a9196c
--- /dev/null
+++ b/ICSharpCode.Decompiler/Util/DelegateComparer.cs
@@ -0,0 +1,39 @@
+#nullable enable
+// Copyright (c) 2010-2013 AlphaSierraPapa for the SharpDevelop Team
+// 
+// Permission is hereby granted, free of charge, to any person obtaining a copy of this
+// software and associated documentation files (the "Software"), to deal in the Software
+// without restriction, including without limitation the rights to use, copy, modify, merge,
+// publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons
+// to whom the Software is furnished to do so, subject to the following conditions:
+// 
+// The above copyright notice and this permission notice shall be included in all copies or
+// substantial portions of the Software.
+// 
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
+// INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR
+// PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE
+// FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
+// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+// DEALINGS IN THE SOFTWARE.
+
+using System;
+using System.Collections.Generic;
+
+namespace ICSharpCode.Decompiler.Util
+{
+	public class DelegateComparer<T> : IComparer<T>
+	{
+		private readonly Func<T?, T?, int> func;
+
+		public DelegateComparer(Func<T?, T?, int> func)
+		{
+			this.func = func ?? throw new ArgumentNullException(nameof(func));
+		}
+
+		public int Compare(T? x, T? y)
+		{
+			return func(x, y);
+		}
+	}
+}
diff --git a/ILSpy/Properties/Resources.Designer.cs b/ILSpy/Properties/Resources.Designer.cs
index 7e9235cae..ce6597717 100644
--- a/ILSpy/Properties/Resources.Designer.cs
+++ b/ILSpy/Properties/Resources.Designer.cs
@@ -1325,6 +1325,15 @@ namespace ICSharpCode.ILSpy.Properties {
             }
         }
         
+        /// <summary>
+        ///   Looks up a localized string similar to Sort custom attributes.
+        /// </summary>
+        public static string DecompilerSettings_SortCustomAttributes {
+            get {
+                return ResourceManager.GetString("DecompilerSettings.SortCustomAttributes", resourceCulture);
+            }
+        }
+        
         /// <summary>
         ///   Looks up a localized string similar to Detect switch on integer even if IL code does not use a jump table.
         /// </summary>
diff --git a/ILSpy/Properties/Resources.resx b/ILSpy/Properties/Resources.resx
index a6e65ca67..dd575638a 100644
--- a/ILSpy/Properties/Resources.resx
+++ b/ILSpy/Properties/Resources.resx
@@ -462,6 +462,9 @@ Are you sure you want to continue?</value>
   <data name="DecompilerSettings.ShowInfoFromDebugSymbolsIfAvailable" xml:space="preserve">
     <value>Show info from debug symbols, if available</value>
   </data>
+  <data name="DecompilerSettings.SortCustomAttributes" xml:space="preserve">
+    <value>Sort custom attributes</value>
+  </data>
   <data name="DecompilerSettings.SparseIntegerSwitch" xml:space="preserve">
     <value>Detect switch on integer even if IL code does not use a jump table</value>
   </data>
diff --git a/ILSpy/Properties/Resources.zh-Hans.resx b/ILSpy/Properties/Resources.zh-Hans.resx
index 8e46de32e..472ac2503 100644
--- a/ILSpy/Properties/Resources.zh-Hans.resx
+++ b/ILSpy/Properties/Resources.zh-Hans.resx
@@ -435,6 +435,9 @@
   <data name="DecompilerSettings.ShowInfoFromDebugSymbolsIfAvailable" xml:space="preserve">
     <value>显示调试符号中的信息(如果可用)</value>
   </data>
+  <data name="DecompilerSettings.SortCustomAttributes" xml:space="preserve">
+    <value />
+  </data>
   <data name="DecompilerSettings.SparseIntegerSwitch" xml:space="preserve">
     <value>检测整型 switch 即使 IL 代码不使用跳转表</value>
   </data>

From 6312ab18aee0ff48301fe2f1578dbd67543bebf6 Mon Sep 17 00:00:00 2001
From: Siegfried Pammer <siegfriedpammer@gmail.com>
Date: Fri, 21 Feb 2025 07:11:42 +0100
Subject: [PATCH 16/49] #2716: Fix typo

---
 ICSharpCode.Decompiler/CSharp/Syntax/TypeSystemAstBuilder.cs | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/ICSharpCode.Decompiler/CSharp/Syntax/TypeSystemAstBuilder.cs b/ICSharpCode.Decompiler/CSharp/Syntax/TypeSystemAstBuilder.cs
index f217717f1..c9ae2a66d 100644
--- a/ICSharpCode.Decompiler/CSharp/Syntax/TypeSystemAstBuilder.cs
+++ b/ICSharpCode.Decompiler/CSharp/Syntax/TypeSystemAstBuilder.cs
@@ -841,7 +841,7 @@ namespace ICSharpCode.Decompiler.CSharp.Syntax
 				result = a.NamedArguments.Length - b.NamedArguments.Length;
 				if (result != 0)
 					return result;
-				for (int i = 0; i < a.FixedArguments.Length; i++)
+				for (int i = 0; i < a.NamedArguments.Length; i++)
 				{
 					var argA = a.NamedArguments[i];
 					var argB = b.NamedArguments[i];

From 292c21d68efd776ff658de4a941f36df14d2bc19 Mon Sep 17 00:00:00 2001
From: Siegfried Pammer <siegfriedpammer@gmail.com>
Date: Fri, 21 Feb 2025 07:32:32 +0100
Subject: [PATCH 17/49] #3401: Treat class name as single name and not as path.

---
 .../CSharp/ProjectDecompiler/WholeProjectDecompiler.cs          | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/ICSharpCode.Decompiler/CSharp/ProjectDecompiler/WholeProjectDecompiler.cs b/ICSharpCode.Decompiler/CSharp/ProjectDecompiler/WholeProjectDecompiler.cs
index da5671559..283e2cb94 100644
--- a/ICSharpCode.Decompiler/CSharp/ProjectDecompiler/WholeProjectDecompiler.cs
+++ b/ICSharpCode.Decompiler/CSharp/ProjectDecompiler/WholeProjectDecompiler.cs
@@ -238,7 +238,7 @@ namespace ICSharpCode.Decompiler.CSharp.ProjectDecompiler
 			string GetFileFileNameForHandle(TypeDefinitionHandle h)
 			{
 				var type = metadata.GetTypeDefinition(h);
-				string file = SanitizeFileName(metadata.GetString(type.Name) + ".cs");
+				string file = CleanUpFileName(metadata.GetString(type.Name) + ".cs");
 				string ns = metadata.GetString(type.Namespace);
 				if (string.IsNullOrEmpty(ns))
 				{

From 31bbcf41bcb0dcd0b3e019bc56fa158d8ac37f70 Mon Sep 17 00:00:00 2001
From: Siegfried Pammer <siegfriedpammer@gmail.com>
Date: Fri, 21 Feb 2025 09:14:23 +0100
Subject: [PATCH 18/49] Fix handling of file extensions in project export.

---
 .../WholeProjectDecompiler.cs                 | 28 +++++++++++--------
 .../DebugInfo/PortablePdbWriter.cs            |  2 +-
 .../BamlResourceNodeFactory.cs                |  2 +-
 ILSpy/Commands/GeneratePdbContextMenuEntry.cs |  2 +-
 ILSpy/Commands/SelectPdbContextMenuEntry.cs   |  2 +-
 ILSpy/TextView/DecompilerTextView.cs          |  2 +-
 ILSpy/TreeNodes/AssemblyTreeNode.cs           |  2 +-
 7 files changed, 22 insertions(+), 18 deletions(-)

diff --git a/ICSharpCode.Decompiler/CSharp/ProjectDecompiler/WholeProjectDecompiler.cs b/ICSharpCode.Decompiler/CSharp/ProjectDecompiler/WholeProjectDecompiler.cs
index 283e2cb94..ec3e955b2 100644
--- a/ICSharpCode.Decompiler/CSharp/ProjectDecompiler/WholeProjectDecompiler.cs
+++ b/ICSharpCode.Decompiler/CSharp/ProjectDecompiler/WholeProjectDecompiler.cs
@@ -19,6 +19,7 @@
 using System;
 using System.Collections.Concurrent;
 using System.Collections.Generic;
+using System.Diagnostics;
 using System.IO;
 using System.Linq;
 using System.Reflection.Metadata;
@@ -135,7 +136,7 @@ namespace ICSharpCode.Decompiler.CSharp.ProjectDecompiler
 
 		public void DecompileProject(MetadataFile file, string targetDirectory, CancellationToken cancellationToken = default(CancellationToken))
 		{
-			string projectFileName = Path.Combine(targetDirectory, CleanUpFileName(file.Name) + ".csproj");
+			string projectFileName = Path.Combine(targetDirectory, CleanUpFileName(file.Name, ".csproj"));
 			using (var writer = new StreamWriter(projectFileName))
 			{
 				DecompileProject(file, targetDirectory, writer, cancellationToken);
@@ -238,7 +239,7 @@ namespace ICSharpCode.Decompiler.CSharp.ProjectDecompiler
 			string GetFileFileNameForHandle(TypeDefinitionHandle h)
 			{
 				var type = metadata.GetTypeDefinition(h);
-				string file = CleanUpFileName(metadata.GetString(type.Name) + ".cs");
+				string file = CleanUpFileName(metadata.GetString(type.Name), ".cs");
 				string ns = metadata.GetString(type.Namespace);
 				if (string.IsNullOrEmpty(ns))
 				{
@@ -339,8 +340,7 @@ namespace ICSharpCode.Decompiler.CSharp.ProjectDecompiler
 						{
 							foreach (var (name, value) in resourcesFile)
 							{
-								string fileName = SanitizeFileName(name)
-									.Replace('/', Path.DirectorySeparatorChar);
+								string fileName = SanitizeFileName(name);
 								string dirName = Path.GetDirectoryName(fileName);
 								if (!string.IsNullOrEmpty(dirName) && directories.Add(dirName))
 								{
@@ -609,9 +609,14 @@ namespace ICSharpCode.Decompiler.CSharp.ProjectDecompiler
 		/// <summary>
 		/// Cleans up a node name for use as a file name.
 		/// </summary>
-		public static string CleanUpFileName(string text)
+		public static string CleanUpFileName(string text, string extension)
 		{
-			return CleanUpName(text, separateAtDots: false, treatAsFileName: false);
+			Debug.Assert(!string.IsNullOrEmpty(extension));
+			if (!extension.StartsWith("."))
+				extension = "." + extension;
+			text = text + extension;
+
+			return CleanUpName(text, separateAtDots: false, treatAsFileName: !string.IsNullOrEmpty(extension), treatAsPath: false);
 		}
 
 		/// <summary>
@@ -620,7 +625,7 @@ namespace ICSharpCode.Decompiler.CSharp.ProjectDecompiler
 		/// </summary>
 		public static string SanitizeFileName(string fileName)
 		{
-			return CleanUpName(fileName, separateAtDots: false, treatAsFileName: true);
+			return CleanUpName(fileName, separateAtDots: false, treatAsFileName: true, treatAsPath: true);
 		}
 
 		/// <summary>
@@ -629,7 +634,7 @@ namespace ICSharpCode.Decompiler.CSharp.ProjectDecompiler
 		/// If <paramref name="treatAsFileName"/> is active, we check for file a extension and try to preserve it,
 		/// if it's valid.
 		/// </summary>
-		static string CleanUpName(string text, bool separateAtDots, bool treatAsFileName)
+		static string CleanUpName(string text, bool separateAtDots, bool treatAsFileName, bool treatAsPath)
 		{
 			// Remove anything that could be confused with a rooted path.
 			int pos = text.IndexOf(':');
@@ -692,7 +697,7 @@ namespace ICSharpCode.Decompiler.CSharp.ProjectDecompiler
 					if (separateAtDots)
 						currentSegmentLength = 0;
 				}
-				else if (treatAsFileName && (c is '/' or '\\') && currentSegmentLength > 1)
+				else if (treatAsPath && (c is '/' or '\\') && currentSegmentLength > 1)
 				{
 					// if we treat this as a file name, we've started a new segment
 					b.Append(Path.DirectorySeparatorChar);
@@ -732,13 +737,12 @@ namespace ICSharpCode.Decompiler.CSharp.ProjectDecompiler
 		/// </summary>
 		public static string CleanUpDirectoryName(string text)
 		{
-			return CleanUpName(text, separateAtDots: false, treatAsFileName: false);
+			return CleanUpName(text, separateAtDots: false, treatAsFileName: false, treatAsPath: false);
 		}
 
 		public static string CleanUpPath(string text)
 		{
-			return CleanUpName(text, separateAtDots: true, treatAsFileName: false)
-				.Replace('.', Path.DirectorySeparatorChar);
+			return CleanUpName(text, separateAtDots: true, treatAsFileName: true, treatAsPath: true);
 		}
 
 		static bool IsReservedFileSystemName(string name)
diff --git a/ICSharpCode.Decompiler/DebugInfo/PortablePdbWriter.cs b/ICSharpCode.Decompiler/DebugInfo/PortablePdbWriter.cs
index 3779ace15..792c3e727 100644
--- a/ICSharpCode.Decompiler/DebugInfo/PortablePdbWriter.cs
+++ b/ICSharpCode.Decompiler/DebugInfo/PortablePdbWriter.cs
@@ -92,7 +92,7 @@ namespace ICSharpCode.Decompiler.DebugInfo
 				string ns = settings.UseNestedDirectoriesForNamespaces
 					? WholeProjectDecompiler.CleanUpPath(typeName.Namespace)
 					: WholeProjectDecompiler.CleanUpDirectoryName(typeName.Namespace);
-				return Path.Combine(ns, WholeProjectDecompiler.CleanUpFileName(typeName.Name) + ".cs");
+				return Path.Combine(ns, WholeProjectDecompiler.CleanUpFileName(typeName.Name, ".cs"));
 			}
 
 			var sourceFiles = reader.GetTopLevelTypeDefinitions().Where(t => IncludeTypeWhenGeneratingPdb(file, t, settings)).GroupBy(BuildFileNameFromTypeName).ToList();
diff --git a/ILSpy.BamlDecompiler/BamlResourceNodeFactory.cs b/ILSpy.BamlDecompiler/BamlResourceNodeFactory.cs
index 29c54c100..681a92d5a 100644
--- a/ILSpy.BamlDecompiler/BamlResourceNodeFactory.cs
+++ b/ILSpy.BamlDecompiler/BamlResourceNodeFactory.cs
@@ -62,7 +62,7 @@ namespace ILSpy.BamlDecompiler
 			var typeDefinition = result.TypeName.HasValue ? typeSystem.MainModule.GetTypeDefinition(result.TypeName.Value.TopLevelTypeName) : null;
 			if (typeDefinition != null)
 			{
-				fileName = WholeProjectDecompiler.CleanUpPath(typeDefinition.ReflectionName) + ".xaml";
+				fileName = WholeProjectDecompiler.CleanUpPath(typeDefinition.ReflectionName + ".xaml");
 				var partialTypeInfo = new PartialTypeInfo(typeDefinition);
 				foreach (var member in result.GeneratedMembers)
 				{
diff --git a/ILSpy/Commands/GeneratePdbContextMenuEntry.cs b/ILSpy/Commands/GeneratePdbContextMenuEntry.cs
index 7a0374895..33e10b24d 100644
--- a/ILSpy/Commands/GeneratePdbContextMenuEntry.cs
+++ b/ILSpy/Commands/GeneratePdbContextMenuEntry.cs
@@ -71,7 +71,7 @@ namespace ICSharpCode.ILSpy
 				return;
 			}
 			SaveFileDialog dlg = new SaveFileDialog();
-			dlg.FileName = WholeProjectDecompiler.CleanUpFileName(assembly.ShortName) + ".pdb";
+			dlg.FileName = WholeProjectDecompiler.CleanUpFileName(assembly.ShortName, ".pdb");
 			dlg.Filter = Resources.PortablePDBPdbAllFiles;
 			dlg.InitialDirectory = Path.GetDirectoryName(assembly.FileName);
 			if (dlg.ShowDialog() != true)
diff --git a/ILSpy/Commands/SelectPdbContextMenuEntry.cs b/ILSpy/Commands/SelectPdbContextMenuEntry.cs
index 9c63a8b8c..5d3d95351 100644
--- a/ILSpy/Commands/SelectPdbContextMenuEntry.cs
+++ b/ILSpy/Commands/SelectPdbContextMenuEntry.cs
@@ -38,7 +38,7 @@ namespace ICSharpCode.ILSpy
 			if (assembly == null)
 				return;
 			OpenFileDialog dlg = new OpenFileDialog();
-			dlg.FileName = WholeProjectDecompiler.CleanUpFileName(assembly.ShortName) + ".pdb";
+			dlg.FileName = WholeProjectDecompiler.CleanUpFileName(assembly.ShortName, ".pdb");
 			dlg.Filter = Resources.PortablePDBPdbAllFiles;
 			dlg.InitialDirectory = Path.GetDirectoryName(assembly.FileName);
 			if (dlg.ShowDialog() != true)
diff --git a/ILSpy/TextView/DecompilerTextView.cs b/ILSpy/TextView/DecompilerTextView.cs
index 3b9ea493c..dc0d5edae 100644
--- a/ILSpy/TextView/DecompilerTextView.cs
+++ b/ILSpy/TextView/DecompilerTextView.cs
@@ -1091,7 +1091,7 @@ namespace ICSharpCode.ILSpy.TextView
 			SaveFileDialog dlg = new SaveFileDialog();
 			dlg.DefaultExt = language.FileExtension;
 			dlg.Filter = language.Name + "|*" + language.FileExtension + Properties.Resources.AllFiles;
-			dlg.FileName = WholeProjectDecompiler.CleanUpFileName(treeNodes.First().ToString()) + language.FileExtension;
+			dlg.FileName = WholeProjectDecompiler.CleanUpFileName(treeNodes.First().ToString(), language.FileExtension);
 			if (dlg.ShowDialog() == true)
 			{
 				SaveToDisk(new DecompilationContext(language, treeNodes.ToArray(), options), dlg.FileName);
diff --git a/ILSpy/TreeNodes/AssemblyTreeNode.cs b/ILSpy/TreeNodes/AssemblyTreeNode.cs
index a34882e4d..e2efa8d1a 100644
--- a/ILSpy/TreeNodes/AssemblyTreeNode.cs
+++ b/ILSpy/TreeNodes/AssemblyTreeNode.cs
@@ -475,7 +475,7 @@ namespace ICSharpCode.ILSpy.TreeNodes
 			if (string.IsNullOrEmpty(language.ProjectFileExtension))
 				return false;
 			SaveFileDialog dlg = new SaveFileDialog();
-			dlg.FileName = WholeProjectDecompiler.CleanUpFileName(LoadedAssembly.ShortName) + language.ProjectFileExtension;
+			dlg.FileName = WholeProjectDecompiler.CleanUpFileName(LoadedAssembly.ShortName, language.ProjectFileExtension);
 			dlg.Filter = language.Name + " project|*" + language.ProjectFileExtension + "|" + language.Name + " single file|*" + language.FileExtension + "|All files|*.*";
 			if (dlg.ShowDialog() == true)
 			{

From 3c70224441aade1a8409c3de176b789761ceb7bf Mon Sep 17 00:00:00 2001
From: Siegfried Pammer <siegfriedpammer@gmail.com>
Date: Fri, 21 Feb 2025 11:59:21 +0100
Subject: [PATCH 19/49] Fix detection and display of explicitly implemented
 operators.

---
 .../Output/CSharpAmbienceTests.cs             | 25 +++++++++-
 .../CSharp/OutputVisitor/CSharpAmbience.cs    | 50 +++++++++++++++++--
 .../CSharp/Syntax/TypeSystemAstBuilder.cs     |  4 --
 .../Implementation/MetadataMethod.cs          | 17 +++++++
 4 files changed, 86 insertions(+), 10 deletions(-)

diff --git a/ICSharpCode.Decompiler.Tests/Output/CSharpAmbienceTests.cs b/ICSharpCode.Decompiler.Tests/Output/CSharpAmbienceTests.cs
index 63eec4cd3..446dbde02 100644
--- a/ICSharpCode.Decompiler.Tests/Output/CSharpAmbienceTests.cs
+++ b/ICSharpCode.Decompiler.Tests/Output/CSharpAmbienceTests.cs
@@ -283,7 +283,17 @@ namespace ICSharpCode.Decompiler.Tests.Output
 		[TestCase(ILSpyMainTreeViewMemberFlags, "this[int] : int")]
 		public void Indexer(ConversionFlags flags, string expectedOutput)
 		{
-			var prop = compilation.FindType(typeof(CSharpAmbienceTests.Program)).GetProperties(p => p.IsIndexer).Single();
+			var prop = compilation.FindType(typeof(CSharpAmbienceTests.Program)).GetProperties(p => p.IsIndexer && !p.IsExplicitInterfaceImplementation).Single();
+			ambience.ConversionFlags = flags;
+
+			Assert.That(ambience.ConvertSymbol(prop), Is.EqualTo(expectedOutput));
+		}
+
+		[TestCase(StandardConversionFlags, "int Interface.this[int index] { get; }")]
+		[TestCase(ILSpyMainTreeViewMemberFlags, "Interface.this[int] : int")]
+		public void ExplicitIndexer(ConversionFlags flags, string expectedOutput)
+		{
+			var prop = compilation.FindType(typeof(CSharpAmbienceTests.Program)).GetProperties(p => p.IsIndexer && p.IsExplicitInterfaceImplementation).Single();
 			ambience.ConversionFlags = flags;
 
 			Assert.That(ambience.ConvertSymbol(prop), Is.EqualTo(expectedOutput));
@@ -323,7 +333,12 @@ namespace ICSharpCode.Decompiler.Tests.Output
 		readonly struct ReadonlyStruct { }
 		readonly ref struct ReadonlyRefStruct { }
 
-		class Program
+		interface Interface
+		{
+			int this[int x] { get; }
+		}
+
+		class Program : Interface
 		{
 			int test;
 			const int TEST2 = 2;
@@ -336,6 +351,12 @@ namespace ICSharpCode.Decompiler.Tests.Output
 				}
 			}
 
+			int Interface.this[int index] {
+				get {
+					return index;
+				}
+			}
+
 			public event EventHandler ProgramChanged;
 
 			public event EventHandler SomeEvent {
diff --git a/ICSharpCode.Decompiler/CSharp/OutputVisitor/CSharpAmbience.cs b/ICSharpCode.Decompiler/CSharp/OutputVisitor/CSharpAmbience.cs
index beac7989c..aa290cbd8 100644
--- a/ICSharpCode.Decompiler/CSharp/OutputVisitor/CSharpAmbience.cs
+++ b/ICSharpCode.Decompiler/CSharp/OutputVisitor/CSharpAmbience.cs
@@ -279,9 +279,20 @@ namespace ICSharpCode.Decompiler.CSharp.OutputVisitor
 				ConvertType(member.DeclaringType, writer, formattingPolicy);
 				writer.WriteToken(Roles.Dot, ".");
 			}
+			IType explicitInterfaceType = GetExplicitInterfaceType(member);
+			string name = member.Name;
+			if (explicitInterfaceType != null)
+			{
+				name = name.Substring(name.LastIndexOf('.') + 1);
+			}
 			switch (member.SymbolKind)
 			{
 				case SymbolKind.Indexer:
+					if (explicitInterfaceType != null)
+					{
+						ConvertType(explicitInterfaceType, writer, formattingPolicy);
+						writer.WriteToken(Roles.Dot, ".");
+					}
 					writer.WriteKeyword(Roles.Identifier, "this");
 					break;
 				case SymbolKind.Constructor:
@@ -292,11 +303,16 @@ namespace ICSharpCode.Decompiler.CSharp.OutputVisitor
 					WriteQualifiedName(member.DeclaringType.Name, writer, formattingPolicy);
 					break;
 				case SymbolKind.Operator:
-					switch (member.Name)
+					switch (name)
 					{
 						case "op_Implicit":
 							writer.WriteKeyword(OperatorDeclaration.ImplicitRole, "implicit");
 							writer.Space();
+							if (explicitInterfaceType != null)
+							{
+								ConvertType(explicitInterfaceType, writer, formattingPolicy);
+								writer.WriteToken(Roles.Dot, ".");
+							}
 							writer.WriteKeyword(OperatorDeclaration.OperatorKeywordRole, "operator");
 							writer.Space();
 							ConvertType(member.ReturnType, writer, formattingPolicy);
@@ -305,9 +321,14 @@ namespace ICSharpCode.Decompiler.CSharp.OutputVisitor
 						case "op_CheckedExplicit":
 							writer.WriteKeyword(OperatorDeclaration.ExplicitRole, "explicit");
 							writer.Space();
+							if (explicitInterfaceType != null)
+							{
+								ConvertType(explicitInterfaceType, writer, formattingPolicy);
+								writer.WriteToken(Roles.Dot, ".");
+							}
 							writer.WriteKeyword(OperatorDeclaration.OperatorKeywordRole, "operator");
 							writer.Space();
-							if (member.Name == "op_CheckedExplicit")
+							if (name == "op_CheckedExplicit")
 							{
 								writer.WriteToken(OperatorDeclaration.CheckedKeywordRole, "checked");
 								writer.Space();
@@ -315,9 +336,14 @@ namespace ICSharpCode.Decompiler.CSharp.OutputVisitor
 							ConvertType(member.ReturnType, writer, formattingPolicy);
 							break;
 						default:
+							if (explicitInterfaceType != null)
+							{
+								ConvertType(explicitInterfaceType, writer, formattingPolicy);
+								writer.WriteToken(Roles.Dot, ".");
+							}
 							writer.WriteKeyword(OperatorDeclaration.OperatorKeywordRole, "operator");
 							writer.Space();
-							var operatorType = OperatorDeclaration.GetOperatorType(member.Name);
+							var operatorType = OperatorDeclaration.GetOperatorType(name);
 							if (operatorType.HasValue && !((ConversionFlags & ConversionFlags.SupportOperatorChecked) == 0 && OperatorDeclaration.IsChecked(operatorType.Value)))
 							{
 								if (OperatorDeclaration.IsChecked(operatorType.Value))
@@ -335,7 +361,12 @@ namespace ICSharpCode.Decompiler.CSharp.OutputVisitor
 					}
 					break;
 				default:
-					writer.WriteIdentifier(Identifier.Create(member.Name));
+					if (explicitInterfaceType != null)
+					{
+						ConvertType(explicitInterfaceType, writer, formattingPolicy);
+						writer.WriteToken(Roles.Dot, ".");
+					}
+					writer.WriteIdentifier(Identifier.Create(name));
 					break;
 			}
 			WriteTypeParameters(node, writer, formattingPolicy);
@@ -407,6 +438,17 @@ namespace ICSharpCode.Decompiler.CSharp.OutputVisitor
 			astType.AcceptVisitor(new CSharpOutputVisitor(writer, formattingPolicy));
 		}
 
+		IType GetExplicitInterfaceType(IMember member)
+		{
+			if (member.IsExplicitInterfaceImplementation)
+			{
+				var baseMember = member.ExplicitlyImplementedInterfaceMembers.FirstOrDefault();
+				if (baseMember != null)
+					return baseMember.DeclaringType;
+			}
+			return null;
+		}
+
 		public string ConvertConstantValue(object constantValue)
 		{
 			return TextWriterTokenWriter.PrintPrimitiveValue(constantValue);
diff --git a/ICSharpCode.Decompiler/CSharp/Syntax/TypeSystemAstBuilder.cs b/ICSharpCode.Decompiler/CSharp/Syntax/TypeSystemAstBuilder.cs
index c9ae2a66d..63b697d75 100644
--- a/ICSharpCode.Decompiler/CSharp/Syntax/TypeSystemAstBuilder.cs
+++ b/ICSharpCode.Decompiler/CSharp/Syntax/TypeSystemAstBuilder.cs
@@ -1784,10 +1784,6 @@ namespace ICSharpCode.Decompiler.CSharp.Syntax
 				case SymbolKind.Event:
 					return ConvertEvent((IEvent)entity);
 				case SymbolKind.Method:
-					if (entity.Name.Contains(".op_"))
-					{
-						goto case SymbolKind.Operator;
-					}
 					return ConvertMethod((IMethod)entity);
 				case SymbolKind.Operator:
 					return ConvertOperator((IMethod)entity);
diff --git a/ICSharpCode.Decompiler/TypeSystem/Implementation/MetadataMethod.cs b/ICSharpCode.Decompiler/TypeSystem/Implementation/MetadataMethod.cs
index 83946c9fa..7173d1850 100644
--- a/ICSharpCode.Decompiler/TypeSystem/Implementation/MetadataMethod.cs
+++ b/ICSharpCode.Decompiler/TypeSystem/Implementation/MetadataMethod.cs
@@ -96,6 +96,23 @@ namespace ICSharpCode.Decompiler.TypeSystem.Implementation
 					this.symbolKind = SymbolKind.Destructor;
 				}
 			}
+			else if ((attributes & MethodAttributes.Static) != 0 && typeParameters.Length == 0)
+			{
+				// Operators that are explicit interface implementations are not marked
+				// with MethodAttributes.SpecialName or MethodAttributes.RTSpecialName
+				string name = this.Name;
+				int index = name.LastIndexOf('.');
+				if (index > 0)
+				{
+					name = name.Substring(index + 1);
+
+					if (name.StartsWith("op_", StringComparison.Ordinal)
+						&& CSharp.Syntax.OperatorDeclaration.GetOperatorType(name) != null)
+					{
+						this.symbolKind = SymbolKind.Operator;
+					}
+				}
+			}
 			this.IsExtensionMethod = (attributes & MethodAttributes.Static) == MethodAttributes.Static
 				&& (module.TypeSystemOptions & TypeSystemOptions.ExtensionMethods) == TypeSystemOptions.ExtensionMethods
 				&& def.GetCustomAttributes().HasKnownAttribute(metadata, KnownAttribute.Extension);

From d31ed51569b4d6c2d2bbb0526881594b1a4b3b7d Mon Sep 17 00:00:00 2001
From: Siegfried Pammer <siegfriedpammer@gmail.com>
Date: Fri, 21 Feb 2025 15:50:50 +0100
Subject: [PATCH 20/49] Fix a few bugs in AnalyzerEntityTreeNode

---
 ILSpy/Analyzers/AnalyzerEntityTreeNode.cs     |  6 ++++-
 .../TreeNodes/AnalyzedModuleTreeNode.cs       | 24 +++++++++++++++++++
 2 files changed, 29 insertions(+), 1 deletion(-)

diff --git a/ILSpy/Analyzers/AnalyzerEntityTreeNode.cs b/ILSpy/Analyzers/AnalyzerEntityTreeNode.cs
index 630bde183..0e1895482 100644
--- a/ILSpy/Analyzers/AnalyzerEntityTreeNode.cs
+++ b/ILSpy/Analyzers/AnalyzerEntityTreeNode.cs
@@ -46,10 +46,14 @@ namespace ICSharpCode.ILSpy.Analyzers
 			MessageBus.Send(this, new NavigateToReferenceEventArgs(new EntityReference(this.Member.ParentModule?.MetadataFile, this.Member.MetadataToken)));
 		}
 
-		public override object ToolTip => Member.ParentModule?.MetadataFile?.FileName;
+		public override object ToolTip => Member?.ParentModule?.MetadataFile?.FileName;
 
 		public override bool HandleAssemblyListChanged(ICollection<LoadedAssembly> removedAssemblies, ICollection<LoadedAssembly> addedAssemblies)
 		{
+			if (Member == null)
+			{
+				return true;
+			}
 			foreach (LoadedAssembly asm in removedAssemblies)
 			{
 				if (this.Member.ParentModule.MetadataFile == asm.GetMetadataFileOrNull())
diff --git a/ILSpy/Analyzers/TreeNodes/AnalyzedModuleTreeNode.cs b/ILSpy/Analyzers/TreeNodes/AnalyzedModuleTreeNode.cs
index a85096147..157cb4ae4 100644
--- a/ILSpy/Analyzers/TreeNodes/AnalyzedModuleTreeNode.cs
+++ b/ILSpy/Analyzers/TreeNodes/AnalyzedModuleTreeNode.cs
@@ -17,9 +17,12 @@
 // DEALINGS IN THE SOFTWARE.
 
 using System;
+using System.Collections.Generic;
 using System.Windows;
 
 using ICSharpCode.Decompiler.TypeSystem;
+using ICSharpCode.ILSpyX;
+using ICSharpCode.ILSpyX.TreeView;
 using ICSharpCode.ILSpyX.TreeView.PlatformAbstractions;
 
 namespace ICSharpCode.ILSpy.Analyzers.TreeNodes
@@ -38,6 +41,8 @@ namespace ICSharpCode.ILSpy.Analyzers.TreeNodes
 
 		public override object Text => analyzedModule.AssemblyName;
 
+		public override object ToolTip => analyzedModule.MetadataFile?.FileName;
+
 		protected override void LoadChildren()
 		{
 			foreach (var lazy in Analyzers)
@@ -62,5 +67,24 @@ namespace ICSharpCode.ILSpy.Analyzers.TreeNodes
 		}
 
 		public override IEntity Member => null;
+
+		public override bool HandleAssemblyListChanged(ICollection<LoadedAssembly> removedAssemblies, ICollection<LoadedAssembly> addedAssemblies)
+		{
+			if (analyzedModule == null)
+			{
+				return true;
+			}
+			foreach (LoadedAssembly asm in removedAssemblies)
+			{
+				if (this.analyzedModule.MetadataFile == asm.GetMetadataFileOrNull())
+					return false; // remove this node
+			}
+			this.Children.RemoveAll(
+				delegate (SharpTreeNode n) {
+					AnalyzerTreeNode an = n as AnalyzerTreeNode;
+					return an == null || !an.HandleAssemblyListChanged(removedAssemblies, addedAssemblies);
+				});
+			return true;
+		}
 	}
 }

From 53522c45f887c4dc8f490815f40a724e4e9e1559 Mon Sep 17 00:00:00 2001
From: Siegfried Pammer <siegfriedpammer@gmail.com>
Date: Fri, 21 Feb 2025 15:52:11 +0100
Subject: [PATCH 21/49] #3401: fix missing file extension if name contains ':'

---
 .../ProjectDecompiler/WholeProjectDecompiler.cs       | 11 ++++++-----
 1 file changed, 6 insertions(+), 5 deletions(-)

diff --git a/ICSharpCode.Decompiler/CSharp/ProjectDecompiler/WholeProjectDecompiler.cs b/ICSharpCode.Decompiler/CSharp/ProjectDecompiler/WholeProjectDecompiler.cs
index ec3e955b2..2299b6fec 100644
--- a/ICSharpCode.Decompiler/CSharp/ProjectDecompiler/WholeProjectDecompiler.cs
+++ b/ICSharpCode.Decompiler/CSharp/ProjectDecompiler/WholeProjectDecompiler.cs
@@ -636,13 +636,9 @@ namespace ICSharpCode.Decompiler.CSharp.ProjectDecompiler
 		/// </summary>
 		static string CleanUpName(string text, bool separateAtDots, bool treatAsFileName, bool treatAsPath)
 		{
-			// Remove anything that could be confused with a rooted path.
-			int pos = text.IndexOf(':');
-			if (pos > 0)
-				text = text.Substring(0, pos);
-			text = text.Trim();
 			string extension = null;
 			int currentSegmentLength = 0;
+			// Extract extension from the end of the name, if valid
 			if (treatAsFileName)
 			{
 				// Check if input is a file name, i.e., has a valid extension
@@ -668,6 +664,11 @@ namespace ICSharpCode.Decompiler.CSharp.ProjectDecompiler
 					}
 				}
 			}
+			// Remove anything that could be confused with a rooted path.
+			int pos = text.IndexOf(':');
+			if (pos > 0)
+				text = text.Substring(0, pos);
+			text = text.Trim();
 			// Remove generics
 			pos = text.IndexOf('`');
 			if (pos > 0)

From b0d6fa2276cb56597de4449b70b189c5f08087fe Mon Sep 17 00:00:00 2001
From: ds5678 <49847914+ds5678@users.noreply.github.com>
Date: Thu, 23 Jan 2025 19:49:39 -0800
Subject: [PATCH 22/49] Add support for array initialization based on
 RuntimeHelpers.CreateSpan<T>

---
 .../TestCases/Pretty/InitializerTests.cs      | 13 +++++
 .../IL/Transforms/ExpressionTransforms.cs     |  6 ++
 .../Transforms/TransformArrayInitializers.cs  | 55 +++++++++++++++++++
 3 files changed, 74 insertions(+)

diff --git a/ICSharpCode.Decompiler.Tests/TestCases/Pretty/InitializerTests.cs b/ICSharpCode.Decompiler.Tests/TestCases/Pretty/InitializerTests.cs
index e02f23ff9..c6ac577e3 100644
--- a/ICSharpCode.Decompiler.Tests/TestCases/Pretty/InitializerTests.cs
+++ b/ICSharpCode.Decompiler.Tests/TestCases/Pretty/InitializerTests.cs
@@ -805,6 +805,19 @@ namespace ICSharpCode.Decompiler.Tests.TestCases.Pretty.InitializerTests
 			array[0] = 1;
 			Console.WriteLine(array.Length);
 		}
+
+#if !NET40 && CS70
+		public static ReadOnlySpan<byte> ReadOnlySpanInitializer_ByteArray()
+		{
+			return new byte[3] { 1, 2, 3 };
+		}
+
+		public static ReadOnlySpan<int> ReadOnlySpanInitializer_Int32Array()
+		{
+			return new int[3] { 1, 2, 3 };
+		}
+#endif
+
 		#endregion
 
 		#region Object initializers
diff --git a/ICSharpCode.Decompiler/IL/Transforms/ExpressionTransforms.cs b/ICSharpCode.Decompiler/IL/Transforms/ExpressionTransforms.cs
index ed086d68f..1b8d09264 100644
--- a/ICSharpCode.Decompiler/IL/Transforms/ExpressionTransforms.cs
+++ b/ICSharpCode.Decompiler/IL/Transforms/ExpressionTransforms.cs
@@ -289,6 +289,12 @@ namespace ICSharpCode.Decompiler.IL.Transforms
 				replacement.AcceptVisitor(this);
 				return;
 			}
+			if (TransformArrayInitializers.TransformRuntimeHelpersCreateSpanInitialization(inst, context, out var replacement2))
+			{
+				context.Step("TransformRuntimeHelpersCreateSpanInitialization: single-dim", inst);
+				inst.ReplaceWith(replacement2);
+				return;
+			}
 			base.VisitCall(inst);
 			TransformAssignment.HandleCompoundAssign(inst, context);
 		}
diff --git a/ICSharpCode.Decompiler/IL/Transforms/TransformArrayInitializers.cs b/ICSharpCode.Decompiler/IL/Transforms/TransformArrayInitializers.cs
index 7ddec2e56..b9cb283e8 100644
--- a/ICSharpCode.Decompiler/IL/Transforms/TransformArrayInitializers.cs
+++ b/ICSharpCode.Decompiler/IL/Transforms/TransformArrayInitializers.cs
@@ -143,6 +143,40 @@ namespace ICSharpCode.Decompiler.IL.Transforms
 			return false;
 		}
 
+		internal static bool TransformRuntimeHelpersCreateSpanInitialization(Call inst, StatementTransformContext context, out ILInstruction replacement)
+		{
+			replacement = null;
+			if (!context.Settings.ArrayInitializers)
+				return false;
+			if (MatchRuntimeHelpersCreateSpan(inst, context, out var elementType, out var field))
+			{
+				if (field.HasFlag(System.Reflection.FieldAttributes.HasFieldRVA))
+				{
+					var valuesList = new List<ILInstruction>();
+					var initialValue = field.GetInitialValue(context.PEFile, context.TypeSystem);
+					var elementTypeSize = elementType.GetSize();
+					if (elementTypeSize <= 0 || initialValue.Length % elementTypeSize != 0)
+						return false;
+
+					var size = initialValue.Length / elementTypeSize;
+					if (context.Settings.Utf8StringLiterals &&
+						elementType.IsKnownType(KnownTypeCode.Byte) &&
+						DecodeUTF8String(initialValue, size, out string text))
+					{
+						replacement = new LdStrUtf8(text);
+						return true;
+					}
+					if (DecodeArrayInitializer(elementType, initialValue, new[] { size }, valuesList))
+					{
+						var tempStore = context.Function.RegisterVariable(VariableKind.InitializerTarget, new ArrayType(context.TypeSystem, elementType));
+						replacement = BlockFromInitializer(tempStore, elementType, new[] { size }, valuesList.ToArray());
+						return true;
+					}
+				}
+			}
+			return false;
+		}
+
 		private static unsafe bool DecodeUTF8String(BlobReader blob, int size, out string text)
 		{
 			if (size > blob.RemainingBytes)
@@ -187,6 +221,27 @@ namespace ICSharpCode.Decompiler.IL.Transforms
 			return true;
 		}
 
+		static bool MatchRuntimeHelpersCreateSpan(Call inst, StatementTransformContext context, out IType elementType, out FieldDefinition field)
+		{
+			field = default;
+			elementType = null;
+			IType type = inst.Method.DeclaringType;
+			if (type.Namespace != "System.Runtime.CompilerServices" || type.Name != "RuntimeHelpers" || type.TypeParameterCount != 0)
+				return false;
+			if (inst.Arguments.Count != 1)
+				return false;
+			IMethod method = inst.Method;
+			if (method.Name != "CreateSpan" || method.TypeArguments.Count != 1)
+				return false;
+			elementType = method.TypeArguments[0];
+			if (!inst.Arguments[0].UnwrapConv(ConversionKind.StopGCTracking).MatchLdMemberToken(out var member))
+				return false;
+			if (member.MetadataToken.IsNil)
+				return false;
+			field = context.PEFile.Metadata.GetFieldDefinition((FieldDefinitionHandle)member.MetadataToken);
+			return true;
+		}
+
 		bool DoTransformMultiDim(ILFunction function, Block body, int pos)
 		{
 			if (pos >= body.Instructions.Count - 2)

From bd0fd8db1f8d8d517e3f42ae9dd8f53a3b703c2d Mon Sep 17 00:00:00 2001
From: Siegfried Pammer <siegfriedpammer@gmail.com>
Date: Sat, 22 Feb 2025 09:08:12 +0100
Subject: [PATCH 23/49] Small refactorings: - extract common code - reduce
 nesting

---
 .../Transforms/TransformArrayInitializers.cs  | 90 ++++++++-----------
 1 file changed, 37 insertions(+), 53 deletions(-)

diff --git a/ICSharpCode.Decompiler/IL/Transforms/TransformArrayInitializers.cs b/ICSharpCode.Decompiler/IL/Transforms/TransformArrayInitializers.cs
index b9cb283e8..65d9d05e0 100644
--- a/ICSharpCode.Decompiler/IL/Transforms/TransformArrayInitializers.cs
+++ b/ICSharpCode.Decompiler/IL/Transforms/TransformArrayInitializers.cs
@@ -119,28 +119,13 @@ namespace ICSharpCode.Decompiler.IL.Transforms
 			replacement = null;
 			if (!context.Settings.ArrayInitializers)
 				return false;
-			if (MatchSpanTCtorWithPointerAndSize(inst, context, out var elementType, out var field, out var size))
-			{
-				if (field.HasFlag(System.Reflection.FieldAttributes.HasFieldRVA))
-				{
-					var valuesList = new List<ILInstruction>();
-					var initialValue = field.GetInitialValue(context.PEFile, context.TypeSystem);
-					if (context.Settings.Utf8StringLiterals &&
-						elementType.IsKnownType(KnownTypeCode.Byte) &&
-						DecodeUTF8String(initialValue, size, out string text))
-					{
-						replacement = new LdStrUtf8(text);
-						return true;
-					}
-					if (DecodeArrayInitializer(elementType, initialValue, new[] { size }, valuesList))
-					{
-						var tempStore = context.Function.RegisterVariable(VariableKind.InitializerTarget, new ArrayType(context.TypeSystem, elementType));
-						replacement = BlockFromInitializer(tempStore, elementType, new[] { size }, valuesList.ToArray());
-						return true;
-					}
-				}
-			}
-			return false;
+			if (!MatchSpanTCtorWithPointerAndSize(inst, context, out var elementType, out var field, out var size))
+				return false;
+			if (!field.HasFlag(System.Reflection.FieldAttributes.HasFieldRVA))
+				return false;
+			var initialValue = field.GetInitialValue(context.PEFile, context.TypeSystem);
+			replacement = DecodeArrayInitializerOrUTF8StringLiteral(context, elementType, initialValue, size);
+			return replacement != null;
 		}
 
 		internal static bool TransformRuntimeHelpersCreateSpanInitialization(Call inst, StatementTransformContext context, out ILInstruction replacement)
@@ -148,33 +133,34 @@ namespace ICSharpCode.Decompiler.IL.Transforms
 			replacement = null;
 			if (!context.Settings.ArrayInitializers)
 				return false;
-			if (MatchRuntimeHelpersCreateSpan(inst, context, out var elementType, out var field))
-			{
-				if (field.HasFlag(System.Reflection.FieldAttributes.HasFieldRVA))
-				{
-					var valuesList = new List<ILInstruction>();
-					var initialValue = field.GetInitialValue(context.PEFile, context.TypeSystem);
-					var elementTypeSize = elementType.GetSize();
-					if (elementTypeSize <= 0 || initialValue.Length % elementTypeSize != 0)
-						return false;
+			if (!MatchRuntimeHelpersCreateSpan(inst, context, out var elementType, out var field))
+				return false;
+			if (!field.HasFlag(System.Reflection.FieldAttributes.HasFieldRVA))
+				return false;
+			var initialValue = field.GetInitialValue(context.PEFile, context.TypeSystem);
+			var elementTypeSize = elementType.GetSize();
+			if (elementTypeSize <= 0 || initialValue.Length % elementTypeSize != 0)
+				return false;
+			var size = initialValue.Length / elementTypeSize;
+			replacement = DecodeArrayInitializerOrUTF8StringLiteral(context, elementType, initialValue, size);
+			return replacement != null;
+		}
 
-					var size = initialValue.Length / elementTypeSize;
-					if (context.Settings.Utf8StringLiterals &&
-						elementType.IsKnownType(KnownTypeCode.Byte) &&
-						DecodeUTF8String(initialValue, size, out string text))
-					{
-						replacement = new LdStrUtf8(text);
-						return true;
-					}
-					if (DecodeArrayInitializer(elementType, initialValue, new[] { size }, valuesList))
-					{
-						var tempStore = context.Function.RegisterVariable(VariableKind.InitializerTarget, new ArrayType(context.TypeSystem, elementType));
-						replacement = BlockFromInitializer(tempStore, elementType, new[] { size }, valuesList.ToArray());
-						return true;
-					}
-				}
+		private static ILInstruction DecodeArrayInitializerOrUTF8StringLiteral(StatementTransformContext context, IType elementType, BlobReader initialValue, int size)
+		{
+			if (context.Settings.Utf8StringLiterals && elementType.IsKnownType(KnownTypeCode.Byte)
+				&& DecodeUTF8String(initialValue, size, out string text))
+			{
+				return new LdStrUtf8(text);
 			}
-			return false;
+			var valuesList = new List<ILInstruction>();
+			if (DecodeArrayInitializer(elementType, initialValue, new[] { size }, valuesList))
+			{
+				var tempStore = context.Function.RegisterVariable(VariableKind.InitializerTarget, new ArrayType(context.TypeSystem, elementType));
+				return BlockFromInitializer(tempStore, elementType, new[] { size }, valuesList.ToArray());
+			}
+
+			return null;
 		}
 
 		private static unsafe bool DecodeUTF8String(BlobReader blob, int size, out string text)
@@ -225,15 +211,13 @@ namespace ICSharpCode.Decompiler.IL.Transforms
 		{
 			field = default;
 			elementType = null;
-			IType type = inst.Method.DeclaringType;
-			if (type.Namespace != "System.Runtime.CompilerServices" || type.Name != "RuntimeHelpers" || type.TypeParameterCount != 0)
+			if (!IsRuntimeHelpers(inst.Method.DeclaringType))
 				return false;
 			if (inst.Arguments.Count != 1)
 				return false;
-			IMethod method = inst.Method;
-			if (method.Name != "CreateSpan" || method.TypeArguments.Count != 1)
+			if (inst.Method is not { Name: "CreateSpan", TypeArguments: [var type] })
 				return false;
-			elementType = method.TypeArguments[0];
+			elementType = type;
 			if (!inst.Arguments[0].UnwrapConv(ConversionKind.StopGCTracking).MatchLdMemberToken(out var member))
 				return false;
 			if (member.MetadataToken.IsNil)
@@ -412,7 +396,7 @@ namespace ICSharpCode.Decompiler.IL.Transforms
 			return field != null;
 		}
 
-		static bool IsRuntimeHelpers(IType type) => type is { Name: "RuntimeHelpers", Namespace: "System.Runtime.CompilerServices" };
+		static bool IsRuntimeHelpers(IType type) => type is { Name: "RuntimeHelpers", Namespace: "System.Runtime.CompilerServices", TypeParameterCount: 0 };
 
 		unsafe bool HandleSequentialLocAllocInitializer(Block block, int pos, ILVariable store, ILInstruction locAllocInstruction, out IType elementType, out StObj[] values, out int instructionsToRemove)
 		{

From 38cdf6d50afda104fe87b4e34389a3ba9626a246 Mon Sep 17 00:00:00 2001
From: Siegfried Pammer <siegfriedpammer@gmail.com>
Date: Sat, 22 Feb 2025 11:09:47 +0100
Subject: [PATCH 24/49] Avoid transforming sub pattern of cpblk stackalloc
 initializer

---
 .../IL/Transforms/TransformArrayInitializers.cs       | 11 ++++++++++-
 1 file changed, 10 insertions(+), 1 deletion(-)

diff --git a/ICSharpCode.Decompiler/IL/Transforms/TransformArrayInitializers.cs b/ICSharpCode.Decompiler/IL/Transforms/TransformArrayInitializers.cs
index 65d9d05e0..7a45440b0 100644
--- a/ICSharpCode.Decompiler/IL/Transforms/TransformArrayInitializers.cs
+++ b/ICSharpCode.Decompiler/IL/Transforms/TransformArrayInitializers.cs
@@ -137,6 +137,8 @@ namespace ICSharpCode.Decompiler.IL.Transforms
 				return false;
 			if (!field.HasFlag(System.Reflection.FieldAttributes.HasFieldRVA))
 				return false;
+			if (IsSubPatternOfCpblkInitializer(inst))
+				return false;
 			var initialValue = field.GetInitialValue(context.PEFile, context.TypeSystem);
 			var elementTypeSize = elementType.GetSize();
 			if (elementTypeSize <= 0 || initialValue.Length % elementTypeSize != 0)
@@ -146,6 +148,13 @@ namespace ICSharpCode.Decompiler.IL.Transforms
 			return replacement != null;
 		}
 
+		private static bool IsSubPatternOfCpblkInitializer(Call inst)
+		{
+			if (inst.Parent is not AddressOf { Parent: Call { Parent: Cpblk cpblk } get_Item })
+				return false;
+			return MatchGetStaticFieldAddress(get_Item, out _);
+		}
+
 		private static ILInstruction DecodeArrayInitializerOrUTF8StringLiteral(StatementTransformContext context, IType elementType, BlobReader initialValue, int size)
 		{
 			if (context.Settings.Utf8StringLiterals && elementType.IsKnownType(KnownTypeCode.Byte)
@@ -373,7 +382,7 @@ namespace ICSharpCode.Decompiler.IL.Transforms
 			return true;
 		}
 
-		bool MatchGetStaticFieldAddress(ILInstruction input, out IField field)
+		static bool MatchGetStaticFieldAddress(ILInstruction input, out IField field)
 		{
 			if (input.MatchLdsFlda(out field))
 				return true;

From 3a13d5a69897b68c5a342e4cfb69fbe9819448b4 Mon Sep 17 00:00:00 2001
From: ds5678 <49847914+ds5678@users.noreply.github.com>
Date: Sat, 22 Feb 2025 02:00:01 -0800
Subject: [PATCH 25/49] Allow explicit null termination character

---
 .../TestCases/Pretty/InitializerTests.cs                  | 1 +
 .../IL/Transforms/TransformArrayInitializers.cs           | 8 ++++++--
 2 files changed, 7 insertions(+), 2 deletions(-)

diff --git a/ICSharpCode.Decompiler.Tests/TestCases/Pretty/InitializerTests.cs b/ICSharpCode.Decompiler.Tests/TestCases/Pretty/InitializerTests.cs
index c6ac577e3..bad7cbccd 100644
--- a/ICSharpCode.Decompiler.Tests/TestCases/Pretty/InitializerTests.cs
+++ b/ICSharpCode.Decompiler.Tests/TestCases/Pretty/InitializerTests.cs
@@ -447,6 +447,7 @@ namespace ICSharpCode.Decompiler.Tests.TestCases.Pretty.InitializerTests
 #endif
 #if CS110 && !NET40
 		public static ReadOnlySpan<byte> UTF8Literal => "Hello, world!"u8;
+		public static ReadOnlySpan<byte> UTF8LiteralWithNullTerminator => "Hello, world!\0"u8;
 #endif
 		#endregion
 
diff --git a/ICSharpCode.Decompiler/IL/Transforms/TransformArrayInitializers.cs b/ICSharpCode.Decompiler/IL/Transforms/TransformArrayInitializers.cs
index 7a45440b0..45110c12d 100644
--- a/ICSharpCode.Decompiler/IL/Transforms/TransformArrayInitializers.cs
+++ b/ICSharpCode.Decompiler/IL/Transforms/TransformArrayInitializers.cs
@@ -182,9 +182,13 @@ namespace ICSharpCode.Decompiler.IL.Transforms
 			for (int i = 0; i < size; i++)
 			{
 				byte val = blob.CurrentPointer[i];
-				// If the string has control characters, it's probably binary data and not a string.
-				if (val < 0x20 && val is not ((byte)'\r' or (byte)'\n' or (byte)'\t'))
+				if (val == 0 && i == size - 1 && size > 1)
 				{
+					// Allow explicit null-termination character.
+				}
+				else if (val < 0x20 && val is not ((byte)'\r' or (byte)'\n' or (byte)'\t'))
+				{
+					// If the string has control characters, it's probably binary data and not a string.
 					text = null;
 					return false;
 				}

From d8825bc1a6799ced72935ca5604ad0c5a8d66885 Mon Sep 17 00:00:00 2001
From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com>
Date: Tue, 25 Feb 2025 14:56:59 +0100
Subject: [PATCH 26/49] Bump ossf/scorecard-action from 2.4.0 to 2.4.1 (#3410)

Bumps [ossf/scorecard-action](https://github.com/ossf/scorecard-action) from 2.4.0 to 2.4.1.
- [Release notes](https://github.com/ossf/scorecard-action/releases)
- [Changelog](https://github.com/ossf/scorecard-action/blob/main/RELEASE.md)
- [Commits](https://github.com/ossf/scorecard-action/compare/v2.4.0...v2.4.1)

---
updated-dependencies:
- dependency-name: ossf/scorecard-action
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
---
 .github/workflows/scorecard.yml | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/.github/workflows/scorecard.yml b/.github/workflows/scorecard.yml
index f6f0279ad..74b6daa1e 100644
--- a/.github/workflows/scorecard.yml
+++ b/.github/workflows/scorecard.yml
@@ -29,7 +29,7 @@ jobs:
           persist-credentials: false
 
       - name: "Run analysis"
-        uses: ossf/scorecard-action@v2.4.0 # https://github.com/marketplace/actions/ossf-scorecard-action
+        uses: ossf/scorecard-action@v2.4.1 # https://github.com/marketplace/actions/ossf-scorecard-action
         with:
           results_file: results.sarif
           results_format: sarif

From 5fab18f3a05400ba8ee52fed92d93d3897c01154 Mon Sep 17 00:00:00 2001
From: Siegfried Pammer <siegfriedpammer@gmail.com>
Date: Sun, 2 Mar 2025 23:45:12 +0100
Subject: [PATCH 27/49] Fix #3414: Apply latest session settings before saving
 upon closing the main window

---
 ILSpy/MainWindow.xaml.cs        | 3 +++
 ILSpy/Search/SearchPaneModel.cs | 6 ++++++
 2 files changed, 9 insertions(+)

diff --git a/ILSpy/MainWindow.xaml.cs b/ILSpy/MainWindow.xaml.cs
index e78c4f7f8..298e5ca75 100644
--- a/ILSpy/MainWindow.xaml.cs
+++ b/ILSpy/MainWindow.xaml.cs
@@ -102,6 +102,9 @@ namespace ICSharpCode.ILSpy
 			MessageBus.Send(this, new ApplySessionSettingsEventArgs(sessionSettings));
 
 			sessionSettings.WindowBounds = this.RestoreBounds;
+			// store window state in settings only if it's not minimized
+			if (this.WindowState != WindowState.Minimized)
+				sessionSettings.WindowState = this.WindowState;
 			sessionSettings.DockLayout.Serialize(new(DockManager));
 
 			snapshot.Save();
diff --git a/ILSpy/Search/SearchPaneModel.cs b/ILSpy/Search/SearchPaneModel.cs
index a9f5444fc..f27acf105 100644
--- a/ILSpy/Search/SearchPaneModel.cs
+++ b/ILSpy/Search/SearchPaneModel.cs
@@ -54,6 +54,12 @@ namespace ICSharpCode.ILSpy.Search
 				SearchTerm = e.SearchTerm;
 				Show();
 			};
+			MessageBus<ApplySessionSettingsEventArgs>.Subscribers += ApplySessionSettings;
+		}
+
+		private void ApplySessionSettings(object sender, ApplySessionSettingsEventArgs e)
+		{
+			e.SessionSettings.SelectedSearchMode = SessionSettings.SelectedSearchMode;
 		}
 
 		public SearchModeModel[] SearchModes { get; } = [

From 3e5e81e16c34ef232d93f7083e66fa60ff1aa1cd Mon Sep 17 00:00:00 2001
From: Siegfried Pammer <siegfriedpammer@gmail.com>
Date: Mon, 3 Mar 2025 19:20:47 +0100
Subject: [PATCH 28/49] Roslyn 4.13

---
 Directory.Packages.props | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)

diff --git a/Directory.Packages.props b/Directory.Packages.props
index f9cbde7a7..62dea4ad9 100644
--- a/Directory.Packages.props
+++ b/Directory.Packages.props
@@ -15,8 +15,8 @@
     <PackageVersion Include="K4os.Compression.LZ4" Version="1.3.8" />
     <PackageVersion Include="McMaster.Extensions.CommandLineUtils" Version="4.1.1" />
     <PackageVersion Include="McMaster.Extensions.Hosting.CommandLine" Version="4.1.1" />
-    <PackageVersion Include="Microsoft.CodeAnalysis.CSharp" Version="4.12.0" />
-    <PackageVersion Include="Microsoft.CodeAnalysis.VisualBasic" Version="4.12.0" />
+    <PackageVersion Include="Microsoft.CodeAnalysis.CSharp" Version="4.13.0" />
+    <PackageVersion Include="Microsoft.CodeAnalysis.VisualBasic" Version="4.13.0" />
     <PackageVersion Include="Microsoft.DiaSymReader.Converter.Xml" Version="1.1.0-beta2-22171-02" />
     <PackageVersion Include="Microsoft.DiaSymReader" Version="1.4.0" />
     <PackageVersion Include="Microsoft.DiaSymReader.Native" Version="17.0.0-beta1.21524.1" />

From 5d1219950fb5bf9708ec898a79db042e3c4f40f9 Mon Sep 17 00:00:00 2001
From: jwfx <jwfx@bitmx.net>
Date: Tue, 4 Mar 2025 21:46:29 +0100
Subject: [PATCH 29/49] Fix exception when writing resx files without adding
 any resources

---
 ICSharpCode.Decompiler/Util/ResXResourceWriter.cs | 3 +++
 1 file changed, 3 insertions(+)

diff --git a/ICSharpCode.Decompiler/Util/ResXResourceWriter.cs b/ICSharpCode.Decompiler/Util/ResXResourceWriter.cs
index 80d712353..09fa9f865 100644
--- a/ICSharpCode.Decompiler/Util/ResXResourceWriter.cs
+++ b/ICSharpCode.Decompiler/Util/ResXResourceWriter.cs
@@ -312,6 +312,9 @@ namespace ICSharpCode.Decompiler.Util
 
 		public void Generate()
 		{
+			if (writer == null)
+				InitWriter();
+
 			if (written)
 				throw new InvalidOperationException("The resource is already generated.");
 

From f0f95efa050c844dcce2f573618f6e064742ddd7 Mon Sep 17 00:00:00 2001
From: Siegfried Pammer <siegfriedpammer@gmail.com>
Date: Sat, 8 Mar 2025 19:55:19 +0100
Subject: [PATCH 30/49] Fix StateRangeAnalysis to handle changes in yield
 return codegen in Roslyn 4.13.

---
 .../IL/ControlFlow/StateRangeAnalysis.cs      | 29 +++++++++++++++++--
 1 file changed, 26 insertions(+), 3 deletions(-)

diff --git a/ICSharpCode.Decompiler/IL/ControlFlow/StateRangeAnalysis.cs b/ICSharpCode.Decompiler/IL/ControlFlow/StateRangeAnalysis.cs
index 4d67e9bb4..2314532da 100644
--- a/ICSharpCode.Decompiler/IL/ControlFlow/StateRangeAnalysis.cs
+++ b/ICSharpCode.Decompiler/IL/ControlFlow/StateRangeAnalysis.cs
@@ -21,11 +21,8 @@
 using System;
 using System.Collections.Generic;
 using System.Diagnostics;
-using System.Diagnostics.CodeAnalysis;
 using System.Linq;
-using System.Text;
 using System.Threading;
-using System.Threading.Tasks;
 
 using ICSharpCode.Decompiler.TypeSystem;
 using ICSharpCode.Decompiler.Util;
@@ -225,6 +222,32 @@ namespace ICSharpCode.Decompiler.IL.ControlFlow
 						goto default;
 					}
 				}
+				case StObj stobj when mode == StateRangeAnalysisMode.IteratorDispose:
+				{
+					if (stobj.MatchStFld(out var target, out var field, out var value) && target.MatchLdThis())
+					{
+						if (field.MemberDefinition == stateField && value.MatchLdcI4(-2))
+						{
+							// Roslyn 4.13 sets the state field in Dispose() to mark the iterator as disposed,
+							// don't consider this user code.
+							return stateRange;
+						}
+						else if (value.MatchDefaultOrNullOrZero())
+						{
+							// Roslyn 4.13 clears any local hoisted local variables in Dispose(),
+							// don't consider this user code.
+							return stateRange;
+						}
+						else
+						{
+							goto default;
+						}
+					}
+					else
+					{
+						goto default;
+					}
+				}
 				default:
 					// User code - abort analysis
 					if (mode == StateRangeAnalysisMode.IteratorDispose && !(inst is Leave l && l.IsLeavingFunction))

From cbe8dd43d75a6b17c1b377632f0586c96d37a8ef Mon Sep 17 00:00:00 2001
From: ds5678 <49847914+ds5678@users.noreply.github.com>
Date: Sun, 9 Mar 2025 21:00:36 -0700
Subject: [PATCH 31/49] Fix null check in MatchLegacySwitchOnStringWithDict

Updated the condition for `nullValueCaseBlock` to ensure it is not null and not equal to `defaultBlock`.
---
 ICSharpCode.Decompiler/IL/Transforms/SwitchOnStringTransform.cs | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/ICSharpCode.Decompiler/IL/Transforms/SwitchOnStringTransform.cs b/ICSharpCode.Decompiler/IL/Transforms/SwitchOnStringTransform.cs
index 8302038b8..9f7752951 100644
--- a/ICSharpCode.Decompiler/IL/Transforms/SwitchOnStringTransform.cs
+++ b/ICSharpCode.Decompiler/IL/Transforms/SwitchOnStringTransform.cs
@@ -668,7 +668,7 @@ namespace ICSharpCode.Decompiler.IL.Transforms
 			if (!FixCasesWithoutValue(sections, stringValues))
 				return false;
 			// switch contains case null:
-			if (nullValueCaseBlock != defaultBlock)
+			if (nullValueCaseBlock != null && nullValueCaseBlock != defaultBlock)
 			{
 				if (!AddNullSection(sections, stringValues, nullValueCaseBlock))
 				{

From a1b3b14b0b5967bd4e9588790012829690a2eb79 Mon Sep 17 00:00:00 2001
From: ds5678 <49847914+ds5678@users.noreply.github.com>
Date: Sat, 15 Mar 2025 00:51:53 -0700
Subject: [PATCH 32/49] Add test

---
 .../ICSharpCode.Decompiler.Tests.csproj       |   2 +
 .../ILPrettyTestRunner.cs                     |   6 +
 .../TestCases/ILPretty/Issue3421.cs           |  28 +++++
 .../TestCases/ILPretty/Issue3421.il           | 116 ++++++++++++++++++
 4 files changed, 152 insertions(+)
 create mode 100644 ICSharpCode.Decompiler.Tests/TestCases/ILPretty/Issue3421.cs
 create mode 100644 ICSharpCode.Decompiler.Tests/TestCases/ILPretty/Issue3421.il

diff --git a/ICSharpCode.Decompiler.Tests/ICSharpCode.Decompiler.Tests.csproj b/ICSharpCode.Decompiler.Tests/ICSharpCode.Decompiler.Tests.csproj
index 3ff44e2bb..fc8734e1a 100644
--- a/ICSharpCode.Decompiler.Tests/ICSharpCode.Decompiler.Tests.csproj
+++ b/ICSharpCode.Decompiler.Tests/ICSharpCode.Decompiler.Tests.csproj
@@ -104,6 +104,7 @@
     <None Include="TestCases\ILPretty\Issue1922.il" />
     <None Include="TestCases\ILPretty\Issue1918.il" />
     <None Include="TestCases\ILPretty\Issue2104.il" />
+    <None Include="TestCases\ILPretty\Issue3421.il" />
     <None Include="TestCases\ILPretty\WeirdEnums.il" />
     <None Include="TestCases\ILPretty\ConstantBlobs.il" />
     <None Include="TestCases\ILPretty\CS1xSwitch_Debug.il" />
@@ -129,6 +130,7 @@
     <Compile Include="Output\InsertParenthesesVisitorTests.cs" />
     <Compile Include="ProjectDecompiler\TargetFrameworkTests.cs" />
     <Compile Include="TestAssemblyResolver.cs" />
+    <Compile Include="TestCases\ILPretty\Issue3421.cs" />
     <Compile Include="TestCases\ILPretty\MonoFixed.cs" />
     <Compile Include="TestCases\Pretty\Comparisons.cs" />
     <None Include="TestCases\VBPretty\VBAutomaticEvents.vb" />
diff --git a/ICSharpCode.Decompiler.Tests/ILPrettyTestRunner.cs b/ICSharpCode.Decompiler.Tests/ILPrettyTestRunner.cs
index 04c94d089..5a86d4e81 100644
--- a/ICSharpCode.Decompiler.Tests/ILPrettyTestRunner.cs
+++ b/ICSharpCode.Decompiler.Tests/ILPrettyTestRunner.cs
@@ -207,6 +207,12 @@ namespace ICSharpCode.Decompiler.Tests
 			await Run();
 		}
 
+		[Test]
+		public async Task Issue3421()
+		{
+			await Run();
+		}
+
 		[Test]
 		public async Task Issue2260SwitchString()
 		{
diff --git a/ICSharpCode.Decompiler.Tests/TestCases/ILPretty/Issue3421.cs b/ICSharpCode.Decompiler.Tests/TestCases/ILPretty/Issue3421.cs
new file mode 100644
index 000000000..d09c674fd
--- /dev/null
+++ b/ICSharpCode.Decompiler.Tests/TestCases/ILPretty/Issue3421.cs
@@ -0,0 +1,28 @@
+internal class Issue3421
+{
+	private string name;
+	private object value;
+
+	public virtual void SetValue(object value)
+	{
+		if (name == null)
+		{
+			return;
+		}
+		switch (name)
+		{
+			case "##Name##":
+				return;
+			case "##Value##":
+				this.value = value;
+				return;
+			case "##InnerText##":
+				this.value = value.ToString();
+				return;
+		}
+		if (this.value == null)
+		{
+			this.value = "";
+		}
+	}
+}
diff --git a/ICSharpCode.Decompiler.Tests/TestCases/ILPretty/Issue3421.il b/ICSharpCode.Decompiler.Tests/TestCases/ILPretty/Issue3421.il
new file mode 100644
index 000000000..541c8094b
--- /dev/null
+++ b/ICSharpCode.Decompiler.Tests/TestCases/ILPretty/Issue3421.il
@@ -0,0 +1,116 @@
+#define CORE_ASSEMBLY "System.Runtime"
+
+.assembly extern CORE_ASSEMBLY
+{
+  .publickeytoken = (B0 3F 5F 7F 11 D5 0A 3A )                         // .?_....:
+  .ver 4:0:0:0
+}
+
+.class private auto ansi beforefieldinit Issue3421
+    extends [CORE_ASSEMBLY]System.Object
+{
+    // Fields
+    .field private string name
+    .field private object 'value'
+    .field private static class [CORE_ASSEMBLY]System.Collections.Generic.Dictionary`2<string, int32> '<>f__switch$map1D'
+    .custom instance void [CORE_ASSEMBLY]System.Runtime.CompilerServices.CompilerGeneratedAttribute::.ctor() = (
+        01 00 00 00
+    )
+
+    // Methods
+    .method public hidebysig virtual 
+        instance void SetValue (
+            object 'value'
+        ) cil managed 
+    {
+        // Method begins at RVA 0x2050
+        // Header size: 12
+        // Code size: 180 (0xb4)
+        .maxstack 27
+        .locals init (
+            [0] string,
+            [1] class [CORE_ASSEMBLY]System.Collections.Generic.Dictionary`2<string, int32>,
+            [2] int32
+        )
+
+        IL_0000: ldarg.0
+        IL_0001: ldfld string Issue3421::name
+        IL_0006: stloc.0
+        IL_0007: ldloc.0
+        IL_0008: brfalse IL_0093
+
+        IL_000d: ldsfld class [CORE_ASSEMBLY]System.Collections.Generic.Dictionary`2<string, int32> Issue3421::'<>f__switch$map1D'
+        IL_0012: brtrue IL_0048
+
+        IL_0017: ldc.i4.3
+        IL_0018: newobj instance void class [CORE_ASSEMBLY]System.Collections.Generic.Dictionary`2<string, int32>::.ctor(int32)
+        IL_001d: stloc.1
+        IL_001e: ldloc.1
+        IL_001f: ldstr "##Name##"
+        IL_0024: ldc.i4.0
+        IL_0025: callvirt instance void class [CORE_ASSEMBLY]System.Collections.Generic.Dictionary`2<string, int32>::Add(!0, !1)
+        IL_002a: ldloc.1
+        IL_002b: ldstr "##Value##"
+        IL_0030: ldc.i4.1
+        IL_0031: callvirt instance void class [CORE_ASSEMBLY]System.Collections.Generic.Dictionary`2<string, int32>::Add(!0, !1)
+        IL_0036: ldloc.1
+        IL_0037: ldstr "##InnerText##"
+        IL_003c: ldc.i4.2
+        IL_003d: callvirt instance void class [CORE_ASSEMBLY]System.Collections.Generic.Dictionary`2<string, int32>::Add(!0, !1)
+        IL_0042: ldloc.1
+        IL_0043: stsfld class [CORE_ASSEMBLY]System.Collections.Generic.Dictionary`2<string, int32> Issue3421::'<>f__switch$map1D'
+
+        IL_0048: ldsfld class [CORE_ASSEMBLY]System.Collections.Generic.Dictionary`2<string, int32> Issue3421::'<>f__switch$map1D'
+        IL_004d: ldloc.0
+        IL_004e: ldloca.s 2
+        IL_0050: callvirt instance bool class [CORE_ASSEMBLY]System.Collections.Generic.Dictionary`2<string, int32>::TryGetValue(!0, !1&)
+        IL_0055: brfalse IL_0098
+
+        IL_005a: ldloc.2
+        IL_005b: switch (IL_0071, IL_0076, IL_0082)
+
+        IL_006c: br IL_0098
+
+        IL_0071: br IL_00b3
+
+        IL_0076: ldarg.0
+        IL_0077: ldarg.1
+        IL_0078: stfld object Issue3421::'value'
+        IL_007d: br IL_00b3
+
+        IL_0082: ldarg.0
+        IL_0083: ldarg.1
+        IL_0084: callvirt instance string [CORE_ASSEMBLY]System.Object::ToString()
+        IL_0089: stfld object Issue3421::'value'
+        IL_008e: br IL_00b3
+
+        IL_0093: br IL_00b3
+
+        IL_0098: ldarg.0
+        IL_0099: ldfld object Issue3421::'value'
+        IL_009e: brtrue IL_00ae
+
+        IL_00a3: ldarg.0
+        IL_00a4: ldstr ""
+        IL_00a9: stfld object Issue3421::'value'
+
+        IL_00ae: br IL_00b3
+
+        IL_00b3: ret
+    } // end of method Issue3421::SetValue
+
+    .method public hidebysig specialname rtspecialname 
+        instance void .ctor () cil managed 
+    {
+        // Method begins at RVA 0x2110
+        // Header size: 1
+        // Code size: 8 (0x8)
+        .maxstack 8
+
+        IL_0000: ldarg.0
+        IL_0001: call instance void [CORE_ASSEMBLY]System.Object::.ctor()
+        IL_0006: nop
+        IL_0007: ret
+    } // end of method Issue3421::.ctor
+
+} // end of class Issue3421

From e4000c8a5c74fabe6f1cf5051a1cfdd9e99b466f Mon Sep 17 00:00:00 2001
From: ds5678 <49847914+ds5678@users.noreply.github.com>
Date: Sat, 15 Mar 2025 01:29:58 -0700
Subject: [PATCH 33/49] Enhance null handling in switch transformations

- Updated `Issue3421.cs`.
- Updated `MatchLegacySwitchOnStringWithDict` to check for `leaveContainer` and handle null sections accordingly.
- Introduced an overload for `AddNullSection` to accept `ILInstruction` as the body, improving flexibility.
- Modified existing `AddNullSection` to utilize the new overload, allowing for varied body types in `SwitchSection`.
---
 .../TestCases/ILPretty/Issue3421.cs                |  6 ++----
 .../IL/Transforms/SwitchOnStringTransform.cs       | 14 +++++++++++++-
 2 files changed, 15 insertions(+), 5 deletions(-)

diff --git a/ICSharpCode.Decompiler.Tests/TestCases/ILPretty/Issue3421.cs b/ICSharpCode.Decompiler.Tests/TestCases/ILPretty/Issue3421.cs
index d09c674fd..1acc63fac 100644
--- a/ICSharpCode.Decompiler.Tests/TestCases/ILPretty/Issue3421.cs
+++ b/ICSharpCode.Decompiler.Tests/TestCases/ILPretty/Issue3421.cs
@@ -5,10 +5,6 @@ internal class Issue3421
 
 	public virtual void SetValue(object value)
 	{
-		if (name == null)
-		{
-			return;
-		}
 		switch (name)
 		{
 			case "##Name##":
@@ -19,6 +15,8 @@ internal class Issue3421
 			case "##InnerText##":
 				this.value = value.ToString();
 				return;
+			case null:
+				return;
 		}
 		if (this.value == null)
 		{
diff --git a/ICSharpCode.Decompiler/IL/Transforms/SwitchOnStringTransform.cs b/ICSharpCode.Decompiler/IL/Transforms/SwitchOnStringTransform.cs
index 9f7752951..f39ff3c86 100644
--- a/ICSharpCode.Decompiler/IL/Transforms/SwitchOnStringTransform.cs
+++ b/ICSharpCode.Decompiler/IL/Transforms/SwitchOnStringTransform.cs
@@ -675,6 +675,13 @@ namespace ICSharpCode.Decompiler.IL.Transforms
 					return false;
 				}
 			}
+			else if (leaveContainer != null && !defaultBlockJump.MatchLeave(leaveContainer))
+			{
+				if (!AddNullSection(sections, stringValues, (Leave)exitBlockJump))
+				{
+					return false;
+				}
+			}
 			context.Step(nameof(MatchLegacySwitchOnStringWithDict), instructions[i]);
 			bool keepAssignmentBefore = false;
 			if (switchValueVar.LoadCount > 2 || switchValue == null)
@@ -741,6 +748,11 @@ namespace ICSharpCode.Decompiler.IL.Transforms
 		}
 
 		bool AddNullSection(List<SwitchSection> sections, List<(string Value, int Index)> stringValues, Block nullValueCaseBlock)
+		{
+			return AddNullSection(sections, stringValues, new Branch(nullValueCaseBlock));
+		}
+
+		bool AddNullSection(List<SwitchSection> sections, List<(string Value, int Index)> stringValues, ILInstruction body)
 		{
 			var label = new LongSet(stringValues.Max(item => item.Index) + 1);
 			var possibleConflicts = sections.Where(sec => sec.Labels.Overlaps(label)).ToArray();
@@ -753,7 +765,7 @@ namespace ICSharpCode.Decompiler.IL.Transforms
 				possibleConflicts[0].Labels = possibleConflicts[0].Labels.ExceptWith(label);
 			}
 			stringValues.Add((null, (int)label.Values.First()));
-			sections.Add(new SwitchSection() { Labels = label, Body = new Branch(nullValueCaseBlock) });
+			sections.Add(new SwitchSection() { Labels = label, Body = body });
 			return true;
 		}
 

From 012f5812e952d2c8f8ce1a14083934bd21a56cbe Mon Sep 17 00:00:00 2001
From: ElektroKill <elektrokilldev@protonmail.com>
Date: Sat, 15 Mar 2025 20:28:07 +0100
Subject: [PATCH 34/49] Fix #3423

---
 .../TestCases/Pretty/EnumTests.cs             | 21 ++++++++++++++++++-
 .../CSharp/TranslatedExpression.cs            |  7 +++----
 2 files changed, 23 insertions(+), 5 deletions(-)

diff --git a/ICSharpCode.Decompiler.Tests/TestCases/Pretty/EnumTests.cs b/ICSharpCode.Decompiler.Tests/TestCases/Pretty/EnumTests.cs
index f66be670d..b7cd02c5c 100644
--- a/ICSharpCode.Decompiler.Tests/TestCases/Pretty/EnumTests.cs
+++ b/ICSharpCode.Decompiler.Tests/TestCases/Pretty/EnumTests.cs
@@ -1,4 +1,4 @@
-// Copyright (c) AlphaSierraPapa for the SharpDevelop Team
+// Copyright (c) AlphaSierraPapa for the SharpDevelop Team
 // 
 // Permission is hereby granted, free of charge, to any person obtaining a copy of this
 // software and associated documentation files (the "Software"), to deal in the Software
@@ -28,6 +28,12 @@ namespace ICSharpCode.Decompiler.Tests.TestCases.Pretty
 			Item2
 		}
 
+		public enum NoZero
+		{
+			Item1 = 1,
+			Item2
+		}
+
 		public enum OutOfOrderMembers
 		{
 			Item1 = 1,
@@ -135,5 +141,18 @@ namespace ICSharpCode.Decompiler.Tests.TestCases.Pretty
 		{
 			return AttributeTargets.Class | AttributeTargets.Delegate;
 		}
+
+		public void EnumInNotZeroCheck(SimpleEnum value, NoZero value2)
+		{
+			if (value != SimpleEnum.Item1)
+			{
+				Console.WriteLine();
+			}
+
+			if (value2 != (NoZero)0)
+			{
+				Console.WriteLine();
+			}
+		}
 	}
 }
diff --git a/ICSharpCode.Decompiler/CSharp/TranslatedExpression.cs b/ICSharpCode.Decompiler/CSharp/TranslatedExpression.cs
index ad864b5ac..e40a975e0 100644
--- a/ICSharpCode.Decompiler/CSharp/TranslatedExpression.cs
+++ b/ICSharpCode.Decompiler/CSharp/TranslatedExpression.cs
@@ -1,4 +1,4 @@
-// Copyright (c) 2014 Daniel Grunwald
+// Copyright (c) 2014 Daniel Grunwald
 // 
 // Permission is hereby granted, free of charge, to any person obtaining a copy of this
 // software and associated documentation files (the "Software"), to deal in the Software
@@ -733,9 +733,8 @@ namespace ICSharpCode.Decompiler.CSharp
 			}
 			else
 			{
-				var zero = new PrimitiveExpression(0)
-					.WithoutILInstruction()
-					.WithRR(new ConstantResolveResult(expressionBuilder.compilation.FindType(KnownTypeCode.Int32), 0));
+				var zero = expressionBuilder
+					.ConvertConstantValue(new ConstantResolveResult(Type, 0), allowImplicitConversion: true);
 				var op = negate ? BinaryOperatorType.Equality : BinaryOperatorType.InEquality;
 				return new BinaryOperatorExpression(Expression, op, zero.Expression)
 					.WithoutILInstruction()

From 49942382d17fdeb17cdbd42ea9222599fddf1e47 Mon Sep 17 00:00:00 2001
From: Siegfried Pammer <siegfriedpammer@gmail.com>
Date: Wed, 19 Mar 2025 22:52:27 +0100
Subject: [PATCH 35/49] Fix #3432: Do not include delegate construction
 use-sites in DetermineCaptureAndDeclarationScopes.

---
 .../IL/Transforms/LocalFunctionDecompiler.cs                | 6 ++++++
 1 file changed, 6 insertions(+)

diff --git a/ICSharpCode.Decompiler/IL/Transforms/LocalFunctionDecompiler.cs b/ICSharpCode.Decompiler/IL/Transforms/LocalFunctionDecompiler.cs
index f00fc48b0..523d6e68c 100644
--- a/ICSharpCode.Decompiler/IL/Transforms/LocalFunctionDecompiler.cs
+++ b/ICSharpCode.Decompiler/IL/Transforms/LocalFunctionDecompiler.cs
@@ -658,6 +658,12 @@ namespace ICSharpCode.Decompiler.IL.Transforms
 			switch (useSite)
 			{
 				case CallInstruction call:
+					if (DelegateConstruction.MatchDelegateConstruction(useSite, out _, out _, out _))
+					{
+						// if this is a delegate construction, skip the use-site, because the capture scope
+						// was already determined when analyzing "this".
+						break;
+					}
 					int firstArgumentIndex = info.Definition.Method.IsStatic ? 0 : 1;
 					for (int i = call.Arguments.Count - 1; i >= firstArgumentIndex; i--)
 					{

From 16600fa6339db0f148a58f2b3f6dfbe0f465d380 Mon Sep 17 00:00:00 2001
From: ElektroKill <elektrokilldev@protonmail.com>
Date: Thu, 20 Mar 2025 18:24:26 +0100
Subject: [PATCH 36/49] Addressed feedback

---
 .../TestCases/Pretty/EnumTests.cs                  |  2 +-
 .../CSharp/TranslatedExpression.cs                 | 14 +++++++++++++-
 2 files changed, 14 insertions(+), 2 deletions(-)

diff --git a/ICSharpCode.Decompiler.Tests/TestCases/Pretty/EnumTests.cs b/ICSharpCode.Decompiler.Tests/TestCases/Pretty/EnumTests.cs
index b7cd02c5c..2b26f7d3a 100644
--- a/ICSharpCode.Decompiler.Tests/TestCases/Pretty/EnumTests.cs
+++ b/ICSharpCode.Decompiler.Tests/TestCases/Pretty/EnumTests.cs
@@ -149,7 +149,7 @@ namespace ICSharpCode.Decompiler.Tests.TestCases.Pretty
 				Console.WriteLine();
 			}
 
-			if (value2 != (NoZero)0)
+			if (value2 != 0)
 			{
 				Console.WriteLine();
 			}
diff --git a/ICSharpCode.Decompiler/CSharp/TranslatedExpression.cs b/ICSharpCode.Decompiler/CSharp/TranslatedExpression.cs
index e40a975e0..66d0eab36 100644
--- a/ICSharpCode.Decompiler/CSharp/TranslatedExpression.cs
+++ b/ICSharpCode.Decompiler/CSharp/TranslatedExpression.cs
@@ -731,11 +731,23 @@ namespace ICSharpCode.Decompiler.CSharp
 					.WithRR(new OperatorResolveResult(boolType, System.Linq.Expressions.ExpressionType.NotEqual,
 													  this.ResolveResult, nullRef.ResolveResult));
 			}
-			else
+			else if (Type.Kind == TypeKind.Enum && Type.GetDefinition() is { } typeDef &&
+					 typeDef.Fields.Any(f => f.GetConstantValue() is { } val && (ulong)CSharpPrimitiveCast.Cast(TypeCode.UInt64, val, false) == 0L))
 			{
 				var zero = expressionBuilder
 					.ConvertConstantValue(new ConstantResolveResult(Type, 0), allowImplicitConversion: true);
 				var op = negate ? BinaryOperatorType.Equality : BinaryOperatorType.InEquality;
+				return new BinaryOperatorExpression(Expression, op, zero.Expression)
+					.WithoutILInstruction()
+					.WithRR(new OperatorResolveResult(boolType, System.Linq.Expressions.ExpressionType.NotEqual,
+						this.ResolveResult, zero.ResolveResult));
+			}
+			else
+			{
+				var zero = new PrimitiveExpression(0)
+					.WithoutILInstruction()
+					.WithRR(new ConstantResolveResult(expressionBuilder.compilation.FindType(KnownTypeCode.Int32), 0));
+				var op = negate ? BinaryOperatorType.Equality : BinaryOperatorType.InEquality;
 				return new BinaryOperatorExpression(Expression, op, zero.Expression)
 					.WithoutILInstruction()
 					.WithRR(new OperatorResolveResult(boolType, System.Linq.Expressions.ExpressionType.NotEqual,

From 73e9771d3ca82048eca5b78f1f6743b66681a352 Mon Sep 17 00:00:00 2001
From: Siegfried Pammer <siegfriedpammer@gmail.com>
Date: Fri, 21 Mar 2025 00:10:00 +0100
Subject: [PATCH 37/49] Fix #2269: LocalFunctionDecompiler misplaces nested
 local functions in ctors

---
 .../IL/Transforms/LocalFunctionDecompiler.cs      | 15 +++++++++++++--
 1 file changed, 13 insertions(+), 2 deletions(-)

diff --git a/ICSharpCode.Decompiler/IL/Transforms/LocalFunctionDecompiler.cs b/ICSharpCode.Decompiler/IL/Transforms/LocalFunctionDecompiler.cs
index 523d6e68c..c377238c1 100644
--- a/ICSharpCode.Decompiler/IL/Transforms/LocalFunctionDecompiler.cs
+++ b/ICSharpCode.Decompiler/IL/Transforms/LocalFunctionDecompiler.cs
@@ -142,9 +142,20 @@ namespace ICSharpCode.Decompiler.IL.Transforms
 					{
 						DetermineCaptureAndDeclarationScope(info, useSite);
 
-						if (context.Function.Method.IsConstructor && localFunction.DeclarationScope == null)
+						if (context.Function.Method.IsConstructor)
 						{
-							localFunction.DeclarationScope = BlockContainer.FindClosestContainer(useSite);
+							if (localFunction.DeclarationScope == null)
+							{
+								localFunction.DeclarationScope = BlockContainer.FindClosestContainer(useSite);
+							}
+							else
+							{
+								localFunction.DeclarationScope = FindCommonAncestorInstruction<BlockContainer>(useSite, localFunction.DeclarationScope);
+								if (localFunction.DeclarationScope == null)
+								{
+									localFunction.DeclarationScope = (BlockContainer)context.Function.Body;
+								}
+							}
 						}
 					}
 

From a599aae54d19557e388971b4a56ff0444aeefed0 Mon Sep 17 00:00:00 2001
From: Siegfried Pammer <siegfriedpammer@gmail.com>
Date: Fri, 4 Oct 2019 10:35:35 +0200
Subject: [PATCH 38/49] #1572: Do not generate variable names that match C#
 keywords.

---
 .../TestCases/Pretty/CompoundAssignmentTest.cs   |  4 ++--
 .../TestCases/Pretty/RefLocalsAndReturns.cs      | 16 ++++++++--------
 .../CSharp/OutputVisitor/CSharpOutputVisitor.cs  |  9 ++++++---
 .../IL/Transforms/AssignVariableNames.cs         |  6 ++++--
 4 files changed, 20 insertions(+), 15 deletions(-)

diff --git a/ICSharpCode.Decompiler.Tests/TestCases/Pretty/CompoundAssignmentTest.cs b/ICSharpCode.Decompiler.Tests/TestCases/Pretty/CompoundAssignmentTest.cs
index b0cb624b6..b133ac078 100644
--- a/ICSharpCode.Decompiler.Tests/TestCases/Pretty/CompoundAssignmentTest.cs
+++ b/ICSharpCode.Decompiler.Tests/TestCases/Pretty/CompoundAssignmentTest.cs
@@ -4943,8 +4943,8 @@ namespace ICSharpCode.Decompiler.Tests.TestCases.Pretty
 
 		public void Issue1779(int value)
 		{
-			CustomStruct2 @struct = GetStruct();
-			@struct.IntProp += value;
+			CustomStruct2 customStruct = GetStruct();
+			customStruct.IntProp += value;
 		}
 	}
 }
diff --git a/ICSharpCode.Decompiler.Tests/TestCases/Pretty/RefLocalsAndReturns.cs b/ICSharpCode.Decompiler.Tests/TestCases/Pretty/RefLocalsAndReturns.cs
index 7a65ff10a..192503f48 100644
--- a/ICSharpCode.Decompiler.Tests/TestCases/Pretty/RefLocalsAndReturns.cs
+++ b/ICSharpCode.Decompiler.Tests/TestCases/Pretty/RefLocalsAndReturns.cs
@@ -182,11 +182,11 @@ namespace ICSharpCode.Decompiler.Tests.TestCases.Pretty
 			GetRef<ReadOnlyStruct>().Method();
 
 			// call on a copy, not the original ref:
-			NormalStruct @ref = GetRef<NormalStruct>();
-			@ref.Method();
+			NormalStruct normalStruct = GetRef<NormalStruct>();
+			normalStruct.Method();
 
-			ReadOnlyStruct ref2 = GetRef<ReadOnlyStruct>();
-			ref2.Method();
+			ReadOnlyStruct readOnlyStruct = GetRef<ReadOnlyStruct>();
+			readOnlyStruct.Method();
 		}
 
 		public void CallOnReadOnlyRefReturn()
@@ -293,13 +293,13 @@ namespace ICSharpCode.Decompiler.Tests.TestCases.Pretty
 
 		public void RefReassignment(ref NormalStruct s)
 		{
-			ref NormalStruct @ref = ref GetRef<NormalStruct>();
-			RefReassignment(ref @ref);
+			ref NormalStruct reference = ref GetRef<NormalStruct>();
+			RefReassignment(ref reference);
 			if (s.GetHashCode() == 0)
 			{
-				@ref = ref GetRef<NormalStruct>();
+				reference = ref GetRef<NormalStruct>();
 			}
-			RefReassignment(ref @ref.GetHashCode() == 4 ? ref @ref : ref s);
+			RefReassignment(ref reference.GetHashCode() == 4 ? ref reference : ref s);
 		}
 
 		public static void Main(string[] args)
diff --git a/ICSharpCode.Decompiler/CSharp/OutputVisitor/CSharpOutputVisitor.cs b/ICSharpCode.Decompiler/CSharp/OutputVisitor/CSharpOutputVisitor.cs
index a462ccb9f..a4f448d2d 100644
--- a/ICSharpCode.Decompiler/CSharp/OutputVisitor/CSharpOutputVisitor.cs
+++ b/ICSharpCode.Decompiler/CSharp/OutputVisitor/CSharpOutputVisitor.cs
@@ -1,4 +1,4 @@
-// Copyright (c) 2010-2020 AlphaSierraPapa for the SharpDevelop Team
+// Copyright (c) 2010-2020 AlphaSierraPapa for the SharpDevelop Team
 // 
 // Permission is hereby granted, free of charge, to any person obtaining a copy of this
 // software and associated documentation files (the "Software"), to deal in the Software
@@ -426,8 +426,9 @@ namespace ICSharpCode.Decompiler.CSharp.OutputVisitor
 
 		/// <summary>
 		/// Determines whether the specified identifier is a keyword in the given context.
+		/// If <paramref name="context"/> is <see langword="null" /> all keywords are treated as unconditional.
 		/// </summary>
-		public static bool IsKeyword(string identifier, AstNode context)
+		public static bool IsKeyword(string identifier, AstNode context = null)
 		{
 			// only 2-10 char lower-case identifiers can be keywords
 			if (identifier.Length > maxKeywordLength || identifier.Length < 2 || identifier[0] < 'a')
@@ -440,10 +441,12 @@ namespace ICSharpCode.Decompiler.CSharp.OutputVisitor
 			}
 			if (queryKeywords.Contains(identifier))
 			{
-				return context.Ancestors.Any(ancestor => ancestor is QueryExpression);
+				return context == null || context.Ancestors.Any(ancestor => ancestor is QueryExpression);
 			}
 			if (identifier == "await")
 			{
+				if (context == null)
+					return true;
 				foreach (AstNode ancestor in context.Ancestors)
 				{
 					// with lambdas/anonymous methods,
diff --git a/ICSharpCode.Decompiler/IL/Transforms/AssignVariableNames.cs b/ICSharpCode.Decompiler/IL/Transforms/AssignVariableNames.cs
index bee806143..c3ce39109 100644
--- a/ICSharpCode.Decompiler/IL/Transforms/AssignVariableNames.cs
+++ b/ICSharpCode.Decompiler/IL/Transforms/AssignVariableNames.cs
@@ -657,8 +657,10 @@ namespace ICSharpCode.Decompiler.IL.Transforms
 
 			if (name.Length == 0)
 				return "obj";
-			else
-				return char.ToLower(name[0]) + name.Substring(1);
+			string lowerCaseName = char.ToLower(name[0]) + name.Substring(1);
+			if (CSharp.OutputVisitor.CSharpOutputVisitor.IsKeyword(lowerCaseName))
+				return null;
+			return lowerCaseName;
 		}
 
 		internal static IType GuessType(IType variableType, ILInstruction inst, ILTransformContext context)

From 03aecf047dcdb55ab7a8009ad37f0c1c77b6a8d0 Mon Sep 17 00:00:00 2001
From: Siegfried Pammer <siegfriedpammer@gmail.com>
Date: Sun, 2 Mar 2025 11:30:57 +0100
Subject: [PATCH 39/49] Add VariableScope and rework AssignVariableNames step
 to support renaming parameters of nested ILFunctions in the future.

---
 .../TestCases/Pretty/DelegateConstruction.cs  |   6 +-
 .../CSharp/ExpressionBuilder.cs               |   7 +-
 .../CSharp/StatementBuilder.cs                |  10 +
 .../IL/ControlFlow/AsyncAwaitDecompiler.cs    |   1 +
 .../IL/ControlFlow/YieldReturnDecompiler.cs   |   1 +
 ICSharpCode.Decompiler/IL/ILReader.cs         |   4 +
 .../IL/Instructions/ILFunction.cs             |   5 +
 .../IL/Transforms/AssignVariableNames.cs      | 684 +++++++++++-------
 .../Util/CollectionExtensions.cs              |   2 +-
 9 files changed, 438 insertions(+), 282 deletions(-)

diff --git a/ICSharpCode.Decompiler.Tests/TestCases/Pretty/DelegateConstruction.cs b/ICSharpCode.Decompiler.Tests/TestCases/Pretty/DelegateConstruction.cs
index c87ca9a7d..fd76c9e23 100644
--- a/ICSharpCode.Decompiler.Tests/TestCases/Pretty/DelegateConstruction.cs
+++ b/ICSharpCode.Decompiler.Tests/TestCases/Pretty/DelegateConstruction.cs
@@ -377,10 +377,10 @@ namespace ICSharpCode.Decompiler.Tests.TestCases.Pretty.DelegateConstruction
 		public static void NameConflict2(int j)
 		{
 			List<Action<int>> list = new List<Action<int>>();
-			for (int k = 0; k < 10; k++)
+			for (int i = 0; i < 10; i++)
 			{
-				list.Add(delegate (int i) {
-					Console.WriteLine(i);
+				list.Add(delegate (int k) {
+					Console.WriteLine(k);
 				});
 			}
 		}
diff --git a/ICSharpCode.Decompiler/CSharp/ExpressionBuilder.cs b/ICSharpCode.Decompiler/CSharp/ExpressionBuilder.cs
index 63737a2d4..e95134ed4 100644
--- a/ICSharpCode.Decompiler/CSharp/ExpressionBuilder.cs
+++ b/ICSharpCode.Decompiler/CSharp/ExpressionBuilder.cs
@@ -2547,6 +2547,11 @@ namespace ICSharpCode.Decompiler.CSharp
 			foreach (var parameter in parameters)
 			{
 				var pd = astBuilder.ConvertParameter(parameter);
+				if (variables.TryGetValue(i, out var v))
+				{
+					pd.AddAnnotation(new ILVariableResolveResult(v, parameters[i].Type));
+					pd.Name = v.Name;
+				}
 				if (string.IsNullOrEmpty(pd.Name) && !pd.Type.IsArgList())
 				{
 					// needs to be consistent with logic in ILReader.CreateILVarable(ParameterDefinition)
@@ -2554,8 +2559,6 @@ namespace ICSharpCode.Decompiler.CSharp
 				}
 				if (settings.AnonymousTypes && parameter.Type.ContainsAnonymousType())
 					pd.Type = null;
-				if (variables.TryGetValue(i, out var v))
-					pd.AddAnnotation(new ILVariableResolveResult(v, parameters[i].Type));
 				yield return pd;
 				i++;
 			}
diff --git a/ICSharpCode.Decompiler/CSharp/StatementBuilder.cs b/ICSharpCode.Decompiler/CSharp/StatementBuilder.cs
index 094a51355..d3f2f5069 100644
--- a/ICSharpCode.Decompiler/CSharp/StatementBuilder.cs
+++ b/ICSharpCode.Decompiler/CSharp/StatementBuilder.cs
@@ -1389,6 +1389,16 @@ namespace ICSharpCode.Decompiler.CSharp
 				var astBuilder = exprBuilder.astBuilder;
 				var method = (MethodDeclaration)astBuilder.ConvertEntity(function.ReducedMethod);
 
+				var variables = function.Variables.Where(v => v.Kind == VariableKind.Parameter).ToDictionary(v => v.Index);
+
+				foreach (var (i, p) in method.Parameters.WithIndex())
+				{
+					if (variables.TryGetValue(i, out var v))
+					{
+						p.Name = v.Name;
+					}
+				}
+
 				if (function.Method.HasBody)
 				{
 					var nestedBuilder = new StatementBuilder(
diff --git a/ICSharpCode.Decompiler/IL/ControlFlow/AsyncAwaitDecompiler.cs b/ICSharpCode.Decompiler/IL/ControlFlow/AsyncAwaitDecompiler.cs
index 259367b2d..ff37d9a1e 100644
--- a/ICSharpCode.Decompiler/IL/ControlFlow/AsyncAwaitDecompiler.cs
+++ b/ICSharpCode.Decompiler/IL/ControlFlow/AsyncAwaitDecompiler.cs
@@ -1165,6 +1165,7 @@ namespace ICSharpCode.Decompiler.IL.ControlFlow
 			function.MoveNextMethod = moveNextFunction.Method;
 			function.SequencePointCandidates = moveNextFunction.SequencePointCandidates;
 			function.CodeSize = moveNextFunction.CodeSize;
+			function.LocalVariableSignatureLength = moveNextFunction.LocalVariableSignatureLength;
 			function.IsIterator = IsAsyncEnumerator;
 			moveNextFunction.Variables.Clear();
 			moveNextFunction.ReleaseRef();
diff --git a/ICSharpCode.Decompiler/IL/ControlFlow/YieldReturnDecompiler.cs b/ICSharpCode.Decompiler/IL/ControlFlow/YieldReturnDecompiler.cs
index dbea7d4cf..c5ef712bc 100644
--- a/ICSharpCode.Decompiler/IL/ControlFlow/YieldReturnDecompiler.cs
+++ b/ICSharpCode.Decompiler/IL/ControlFlow/YieldReturnDecompiler.cs
@@ -656,6 +656,7 @@ namespace ICSharpCode.Decompiler.IL.ControlFlow
 			function.MoveNextMethod = moveNextFunction.Method;
 			function.SequencePointCandidates = moveNextFunction.SequencePointCandidates;
 			function.CodeSize = moveNextFunction.CodeSize;
+			function.LocalVariableSignatureLength = moveNextFunction.LocalVariableSignatureLength;
 
 			// Copy-propagate temporaries holding a copy of 'this'.
 			// This is necessary because the old (pre-Roslyn) C# compiler likes to store 'this' in temporary variables.
diff --git a/ICSharpCode.Decompiler/IL/ILReader.cs b/ICSharpCode.Decompiler/IL/ILReader.cs
index 2ec9da40a..226218e12 100644
--- a/ICSharpCode.Decompiler/IL/ILReader.cs
+++ b/ICSharpCode.Decompiler/IL/ILReader.cs
@@ -342,7 +342,10 @@ namespace ICSharpCode.Decompiler.IL
 			if (index < 0)
 				ilVar.Name = "this";
 			else if (string.IsNullOrWhiteSpace(name))
+			{
 				ilVar.Name = "P_" + index;
+				ilVar.HasGeneratedName = true;
+			}
 			else
 				ilVar.Name = name;
 			return ilVar;
@@ -706,6 +709,7 @@ namespace ICSharpCode.Decompiler.IL
 			var function = new ILFunction(this.method, body.GetCodeSize(), this.genericContext, mainContainer, kind);
 			function.Variables.AddRange(parameterVariables);
 			function.Variables.AddRange(localVariables);
+			function.LocalVariableSignatureLength = localVariables.Length;
 			Debug.Assert(stackVariables != null);
 			function.Variables.AddRange(stackVariables);
 			function.Variables.AddRange(variableByExceptionHandler.Values);
diff --git a/ICSharpCode.Decompiler/IL/Instructions/ILFunction.cs b/ICSharpCode.Decompiler/IL/Instructions/ILFunction.cs
index bbf0e6441..eb81ccbce 100644
--- a/ICSharpCode.Decompiler/IL/Instructions/ILFunction.cs
+++ b/ICSharpCode.Decompiler/IL/Instructions/ILFunction.cs
@@ -67,6 +67,11 @@ namespace ICSharpCode.Decompiler.IL
 		/// </summary>
 		public readonly ILVariableCollection Variables;
 
+		/// <summary>
+		/// Gets 
+		/// </summary>
+		public int LocalVariableSignatureLength;
+
 		/// <summary>
 		/// Gets the scope in which the local function is declared.
 		/// Returns null, if this is not a local function.
diff --git a/ICSharpCode.Decompiler/IL/Transforms/AssignVariableNames.cs b/ICSharpCode.Decompiler/IL/Transforms/AssignVariableNames.cs
index c3ce39109..0d1acd261 100644
--- a/ICSharpCode.Decompiler/IL/Transforms/AssignVariableNames.cs
+++ b/ICSharpCode.Decompiler/IL/Transforms/AssignVariableNames.cs
@@ -18,6 +18,7 @@
 
 using System;
 using System.Collections.Generic;
+using System.Collections.Immutable;
 using System.Diagnostics;
 using System.Linq;
 using System.Reflection;
@@ -25,6 +26,7 @@ using System.Reflection.Metadata;
 
 using Humanizer.Inflections;
 
+using ICSharpCode.Decompiler.CSharp;
 using ICSharpCode.Decompiler.CSharp.OutputVisitor;
 using ICSharpCode.Decompiler.TypeSystem;
 using ICSharpCode.Decompiler.TypeSystem.Implementation;
@@ -32,7 +34,7 @@ using ICSharpCode.Decompiler.Util;
 
 namespace ICSharpCode.Decompiler.IL.Transforms
 {
-	public class AssignVariableNames : IILTransform
+	public class AssignVariableNames : ILVisitor<AssignVariableNames.VariableScope, Unit>, IILTransform
 	{
 		static readonly Dictionary<string, string> typeNameToVariableNameDict = new Dictionary<string, string> {
 			{ "System.Boolean", "flag" },
@@ -53,52 +55,83 @@ namespace ICSharpCode.Decompiler.IL.Transforms
 		};
 
 		ILTransformContext context;
-		List<string> currentLowerCaseTypeOrMemberNames;
-		Dictionary<string, int> reservedVariableNames;
-		HashSet<ILVariable> loopCounters;
 		const char maxLoopVariableName = 'n';
-		int numDisplayClassLocals;
 
-		public void Run(ILFunction function, ILTransformContext context)
+		public class VariableScope
 		{
-			this.context = context;
+			readonly ILTransformContext context;
+			readonly VariableScope parentScope;
+			readonly ILFunction function;
+			readonly Dictionary<MethodDefinitionHandle, string> localFunctions = new();
+			readonly Dictionary<ILVariable, string> variableMapping = new(ILVariableEqualityComparer.Instance);
+			readonly string[] assignedLocalSignatureIndices;
+
+			IImmutableSet<string> currentLowerCaseTypeOrMemberNames;
+			Dictionary<string, int> reservedVariableNames;
+			HashSet<ILVariable> loopCounters;
+			int numDisplayClassLocals;
+
+			public VariableScope(ILFunction function, ILTransformContext context, VariableScope parentScope = null)
+			{
+				this.function = function;
+				this.context = context;
+				this.parentScope = parentScope;
+
+				numDisplayClassLocals = 0;
+				assignedLocalSignatureIndices = new string[function.LocalVariableSignatureLength];
+				reservedVariableNames = new Dictionary<string, int>();
+
+				// find all loop counters in the current function
+				loopCounters = new HashSet<ILVariable>();
+				foreach (var inst in TreeTraversal.PreOrder((ILInstruction)function, i => i.Children))
+				{
+					if (inst is ILFunction && inst != function)
+						break;
+					if (inst is BlockContainer { Kind: ContainerKind.For, Blocks: [.., var incrementBlock] })
+					{
+						foreach (var i in incrementBlock.Instructions)
+						{
+							if (HighLevelLoopTransform.MatchIncrement(i, out var variable))
+								loopCounters.Add(variable);
+						}
+					}
+				}
 
-			reservedVariableNames = new Dictionary<string, int>();
-			currentLowerCaseTypeOrMemberNames = new List<string>();
-			var currentLowerCaseMemberNames = CollectAllLowerCaseMemberNames(function.Method.DeclaringTypeDefinition);
-			foreach (var name in currentLowerCaseMemberNames)
-				currentLowerCaseTypeOrMemberNames.Add(name);
-			var currentLowerCaseTypeNames = CollectAllLowerCaseTypeNames(function.Method.DeclaringTypeDefinition);
-			foreach (var name in currentLowerCaseTypeNames)
-			{
-				currentLowerCaseTypeOrMemberNames.Add(name);
-				AddExistingName(reservedVariableNames, name);
-			}
-			loopCounters = CollectLoopCounters(function);
-			foreach (var f in function.Descendants.OfType<ILFunction>())
-			{
-				if (f.Method != null)
+				// if this is the root scope, we also collect all lower-case type and member names
+				// and fixed parameter names to avoid conflicts when naming local variables.
+				if (parentScope == null)
 				{
-					if (IsSetOrEventAccessor(f.Method) && f.Method.Parameters.Count > 0)
+					var currentLowerCaseTypeOrMemberNames = new HashSet<string>(StringComparer.Ordinal);
+					foreach (var name in CollectAllLowerCaseMemberNames(function.Method.DeclaringTypeDefinition))
+						currentLowerCaseTypeOrMemberNames.Add(name);
+					foreach (var name in CollectAllLowerCaseTypeNames(function.Method.DeclaringTypeDefinition))
 					{
-						for (int i = 0; i < f.Method.Parameters.Count - 1; i++)
+						currentLowerCaseTypeOrMemberNames.Add(name);
+						AddExistingName(reservedVariableNames, name);
+					}
+					this.currentLowerCaseTypeOrMemberNames = currentLowerCaseTypeOrMemberNames.ToImmutableHashSet();
+
+					// handle implicit parameters of set or event accessors
+					if (function.Method != null && IsSetOrEventAccessor(function.Method) && function.Parameters.Count > 0)
+					{
+						for (int i = 0; i < function.Method.Parameters.Count - 1; i++)
 						{
-							AddExistingName(reservedVariableNames, f.Method.Parameters[i].Name);
+							AddExistingName(reservedVariableNames, function.Method.Parameters[i].Name);
 						}
-						var lastParameter = f.Method.Parameters.Last();
-						switch (f.Method.AccessorOwner)
+						var lastParameter = function.Method.Parameters.Last();
+						switch (function.Method.AccessorOwner)
 						{
 							case IProperty prop:
-								if (f.Method.AccessorKind == MethodSemanticsAttributes.Setter)
+								if (function.Method.AccessorKind == MethodSemanticsAttributes.Setter)
 								{
 									if (prop.Parameters.Any(p => p.Name == "value"))
 									{
-										f.Warnings.Add("Parameter named \"value\" already present in property signature!");
+										function.Warnings.Add("Parameter named \"value\" already present in property signature!");
 										break;
 									}
-									var variableForLastParameter = f.Variables.FirstOrDefault(v => v.Function == f
+									var variableForLastParameter = function.Variables.FirstOrDefault(v => v.Function == function
 										&& v.Kind == VariableKind.Parameter
-										&& v.Index == f.Method.Parameters.Count - 1);
+										&& v.Index == function.Method.Parameters.Count - 1);
 									if (variableForLastParameter == null)
 									{
 										AddExistingName(reservedVariableNames, lastParameter.Name);
@@ -114,11 +147,11 @@ namespace ICSharpCode.Decompiler.IL.Transforms
 								}
 								break;
 							case IEvent ev:
-								if (f.Method.AccessorKind != MethodSemanticsAttributes.Raiser)
+								if (function.Method.AccessorKind != MethodSemanticsAttributes.Raiser)
 								{
-									var variableForLastParameter = f.Variables.FirstOrDefault(v => v.Function == f
+									var variableForLastParameter = function.Variables.FirstOrDefault(v => v.Function == function
 										&& v.Kind == VariableKind.Parameter
-										&& v.Index == f.Method.Parameters.Count - 1);
+										&& v.Index == function.Method.Parameters.Count - 1);
 									if (variableForLastParameter == null)
 									{
 										AddExistingName(reservedVariableNames, lastParameter.Name);
@@ -140,183 +173,394 @@ namespace ICSharpCode.Decompiler.IL.Transforms
 					}
 					else
 					{
-						foreach (var p in f.Method.Parameters)
-							AddExistingName(reservedVariableNames, p.Name);
+						var variables = function.Variables.Where(v => v.Kind == VariableKind.Parameter && v.Index >= 0).ToDictionary(v => v.Index);
+						foreach (var (i, p) in function.Parameters.WithIndex())
+						{
+							string name = p.Name;
+							if (string.IsNullOrWhiteSpace(name) && p.Type != SpecialType.ArgList)
+							{
+								// needs to be consistent with logic in ILReader.CreateILVarable
+								name = "P_" + i;
+							}
+							if (variables.TryGetValue(i, out var v))
+								variableMapping[v] = name;
+							AddExistingName(reservedVariableNames, name);
+						}
+					}
+
+					static bool IsSetOrEventAccessor(IMethod method)
+					{
+						switch (method.AccessorKind)
+						{
+							case MethodSemanticsAttributes.Setter:
+							case MethodSemanticsAttributes.Adder:
+							case MethodSemanticsAttributes.Remover:
+								return true;
+							default:
+								return false;
+						}
 					}
 				}
 				else
 				{
-					foreach (var p in f.Variables.Where(v => v.Kind == VariableKind.Parameter))
-						AddExistingName(reservedVariableNames, p.Name);
+					this.currentLowerCaseTypeOrMemberNames = parentScope.currentLowerCaseTypeOrMemberNames;
+					var variables = function.Variables.Where(v => v.Kind == VariableKind.Parameter).ToDictionary(v => v.Index);
+
+					foreach (var (i, p) in function.Parameters.WithIndex())
+					{
+						if (function.Kind is ILFunctionKind.Delegate or ILFunctionKind.ExpressionTree
+							&& CSharpDecompiler.IsTransparentIdentifier(p.Name))
+						{
+							AddExistingName(reservedVariableNames, p.Name);
+							if (variables.TryGetValue(i, out var v))
+								variableMapping[v] = p.Name;
+						}
+						if (!parentScope.IsReservedVariableName(p.Name, out _))
+						{
+							AddExistingName(reservedVariableNames, p.Name);
+							if (variables.TryGetValue(i, out var v))
+								variableMapping[v] = p.Name;
+						}
+					}
 				}
 			}
-			numDisplayClassLocals = 0;
-			PerformAssignment(function);
-		}
 
-		static IEnumerable<string> CollectAllLowerCaseMemberNames(ITypeDefinition type)
-		{
-			foreach (var item in type.GetMembers(m => IsLowerCase(m.Name)))
-				yield return item.Name;
-		}
+			public void Add(MethodDefinitionHandle localFunction, string name)
+			{
+				this.localFunctions[localFunction] = name;
+			}
 
-		static IEnumerable<string> CollectAllLowerCaseTypeNames(ITypeDefinition type)
-		{
-			var ns = type.ParentModule.Compilation.GetNamespaceByFullName(type.Namespace);
-			foreach (var item in ns.Types)
+			public string TryGetExistingName(MethodDefinitionHandle localFunction)
 			{
-				if (IsLowerCase(item.Name))
-					yield return item.Name;
+				if (localFunctions.TryGetValue(localFunction, out var name))
+					return name;
+				return parentScope?.TryGetExistingName(localFunction);
 			}
-		}
 
-		static bool IsLowerCase(string name)
-		{
-			return name.Length > 0 && char.ToLower(name[0]) == name[0];
-		}
+			public string TryGetExistingName(ILVariable v)
+			{
+				if (variableMapping.TryGetValue(v, out var name))
+					return name;
+				return parentScope?.TryGetExistingName(v);
+			}
 
-		bool IsSetOrEventAccessor(IMethod method)
-		{
-			switch (method.AccessorKind)
+			public string TryGetExistingName(ILFunction function, int index)
 			{
-				case MethodSemanticsAttributes.Setter:
-				case MethodSemanticsAttributes.Adder:
-				case MethodSemanticsAttributes.Remover:
+				if (this.function == function)
+				{
+					return this.assignedLocalSignatureIndices[index];
+				}
+				else
+				{
+					return parentScope?.TryGetExistingName(function, index);
+				}
+			}
+
+			public void AssignNameToLocalSignatureIndex(ILFunction function, int index, string name)
+			{
+				var scope = this;
+				while (scope != null && scope.function != function)
+					scope = scope.parentScope;
+				Debug.Assert(scope != null);
+				scope.assignedLocalSignatureIndices[index] = name;
+			}
+
+			public bool IsReservedVariableName(string name, out int index)
+			{
+				if (reservedVariableNames.TryGetValue(name, out index))
 					return true;
-				default:
-					return false;
+				return parentScope?.IsReservedVariableName(name, out index) ?? false;
 			}
-		}
 
-		void PerformAssignment(ILFunction function)
-		{
-			var localFunctionMapping = new Dictionary<MethodDefinitionHandle, string>();
-			var variableMapping = new Dictionary<ILVariable, string>(ILVariableEqualityComparer.Instance);
-			var assignedLocalSignatureIndices = new Dictionary<(ILFunction, int), string>();
+			public void ReserveVariableName(string name, int index = 1)
+			{
+				reservedVariableNames[name] = index;
+			}
 
-			foreach (var inst in function.Descendants)
+			public string NextDisplayClassLocal()
 			{
-				if (inst is ILFunction { Kind: ILFunctionKind.LocalFunction } localFunction)
+				return parentScope?.NextDisplayClassLocal() ?? "CS$<>8__locals" + (numDisplayClassLocals++);
+			}
+
+			public bool IsLoopCounter(ILVariable v)
+			{
+				return loopCounters.Contains(v) || (parentScope?.IsLoopCounter(v) == true);
+			}
+
+			public string AssignName(ILVariable v)
+			{
+				// variable has no valid name
+				string newName = v.Name;
+				if (v.HasGeneratedName || !IsValidName(newName))
+				{
+					// don't use the name from the debug symbols if it looks like a generated name
+					// generate a new one based on how the variable is used
+					newName = GenerateNameForVariable(v);
+				}
+				// use the existing name and update index appended to future conflicts
+				string nameWithoutNumber = SplitName(newName, out int newIndex);
+				if (IsReservedVariableName(nameWithoutNumber, out int lastUsedIndex))
 				{
-					// assign names to local functions
-					if (!LocalFunctionDecompiler.ParseLocalFunctionName(localFunction.Name, out _, out var newName) || !IsValidName(newName))
-						newName = null;
-					if (newName == null)
+					if (v.Type.IsKnownType(KnownTypeCode.Int32) && IsLoopCounter(v))
 					{
-						string nameWithoutNumber = "f";
-						if (!reservedVariableNames.TryGetValue(nameWithoutNumber, out int currentIndex))
+						// special case for loop counters,
+						// we don't want them to be named i, i2, ..., but i, j, ...
+						newName = GenerateNameForVariable(v);
+						nameWithoutNumber = newName;
+						newIndex = 1;
+					}
+				}
+				if (IsReservedVariableName(nameWithoutNumber, out lastUsedIndex))
+				{
+					// name without number was already used
+					if (newIndex > lastUsedIndex)
+					{
+						// new index is larger than last, so we can use it
+					}
+					else
+					{
+						// new index is smaller or equal, so we use the next value
+						newIndex = lastUsedIndex + 1;
+					}
+					// resolve conflicts by appending the index to the new name:
+					newName = nameWithoutNumber + newIndex.ToString();
+				}
+				// update the last used index
+				ReserveVariableName(nameWithoutNumber, newIndex);
+				variableMapping.Add(v, newName);
+				return newName;
+			}
+
+			string GenerateNameForVariable(ILVariable variable)
+			{
+				string proposedName = null;
+				if (variable.Type.IsKnownType(KnownTypeCode.Int32))
+				{
+					// test whether the variable might be a loop counter
+					if (loopCounters.Contains(variable))
+					{
+						// For loop variables, use i,j,k,l,m,n
+						for (char c = 'i'; c <= maxLoopVariableName; c++)
+						{
+							if (!IsReservedVariableName(c.ToString(), out _))
+							{
+								proposedName = c.ToString();
+								break;
+							}
+						}
+					}
+				}
+				// The ComponentResourceManager inside InitializeComponent must be named "resources",
+				// otherwise the WinForms designer won't load the Form.
+				if (CSharp.CSharpDecompiler.IsWindowsFormsInitializeComponentMethod(context.Function.Method) && variable.Type.FullName == "System.ComponentModel.ComponentResourceManager")
+				{
+					proposedName = "resources";
+				}
+				if (string.IsNullOrEmpty(proposedName))
+				{
+					var proposedNameForAddress = variable.AddressInstructions.OfType<LdLoca>()
+						.Select(arg => arg.Parent is CallInstruction c ? c.GetParameter(arg.ChildIndex)?.Name : null)
+						.Where(arg => !string.IsNullOrWhiteSpace(arg))
+						.Except(currentLowerCaseTypeOrMemberNames).ToList();
+					if (proposedNameForAddress.Count > 0)
+					{
+						proposedName = proposedNameForAddress[0];
+					}
+				}
+				if (string.IsNullOrEmpty(proposedName))
+				{
+					var proposedNameForStores = new HashSet<string>();
+					foreach (var store in variable.StoreInstructions)
+					{
+						if (store is StLoc stloc)
 						{
-							currentIndex = 1;
+							var name = GetNameFromInstruction(stloc.Value);
+							if (!currentLowerCaseTypeOrMemberNames.Contains(name))
+								proposedNameForStores.Add(name);
 						}
-						int count = Math.Max(1, currentIndex) + 1;
-						reservedVariableNames[nameWithoutNumber] = count;
-						if (count > 1)
+						else if (store is MatchInstruction match && match.SlotInfo == MatchInstruction.SubPatternsSlot)
 						{
-							newName = nameWithoutNumber + count.ToString();
+							var name = GetNameFromInstruction(match.TestedOperand);
+							if (!currentLowerCaseTypeOrMemberNames.Contains(name))
+								proposedNameForStores.Add(name);
 						}
-						else
+						else if (store is PinnedRegion pinnedRegion)
 						{
-							newName = nameWithoutNumber;
+							var name = GetNameFromInstruction(pinnedRegion.Init);
+							if (!currentLowerCaseTypeOrMemberNames.Contains(name))
+								proposedNameForStores.Add(name);
 						}
 					}
-					localFunction.Name = newName;
-					localFunction.ReducedMethod.Name = newName;
-					localFunctionMapping[(MethodDefinitionHandle)localFunction.ReducedMethod.MetadataToken] = newName;
-				}
-				else if (inst is IInstructionWithVariableOperand i)
-				{
-					var v = i.Variable;
-					// if there is already a valid name for the variable slot, just use it
-					if (variableMapping.TryGetValue(v, out string name))
+					if (proposedNameForStores.Count == 1)
 					{
-						v.Name = name;
-						continue;
+						proposedName = proposedNameForStores.Single();
 					}
-					switch (v.Kind)
+				}
+				if (string.IsNullOrEmpty(proposedName))
+				{
+					var proposedNameForLoads = variable.LoadInstructions
+						.Select(arg => GetNameForArgument(arg.Parent, arg.ChildIndex))
+						.Except(currentLowerCaseTypeOrMemberNames).ToList();
+					if (proposedNameForLoads.Count == 1)
 					{
-						case VariableKind.Parameter:
-							// Parameter names are handled in ILReader.CreateILVariable
-							// and CSharpDecompiler.FixParameterNames
-							break;
-						case VariableKind.InitializerTarget: // keep generated names
-							AddExistingName(reservedVariableNames, v.Name);
-							break;
-						case VariableKind.DisplayClassLocal:
-							v.Name = "CS$<>8__locals" + (numDisplayClassLocals++);
-							break;
-						case VariableKind.Local when v.Index != null:
-							if (assignedLocalSignatureIndices.TryGetValue((v.Function, v.Index.Value), out name))
-							{
-								// make sure all local ILVariables that refer to the same slot in the locals signature
-								// are assigned the same name.
-								v.Name = name;
-							}
-							else
-							{
-								v.Name = AssignName(v, variableMapping);
-								// Remember the newly assigned name:
-								assignedLocalSignatureIndices.Add((v.Function, v.Index.Value), v.Name);
-							}
-							break;
-						default:
-							v.Name = AssignName(v, variableMapping);
-							break;
+						proposedName = proposedNameForLoads[0];
 					}
 				}
-				else if (inst is (Call or LdFtn) and IInstructionWithMethodOperand m)
+				if (string.IsNullOrEmpty(proposedName) && variable.Kind == VariableKind.StackSlot)
 				{
-					// update references to local functions
-					if (m.Method is LocalFunctionMethod lf
-						&& localFunctionMapping.TryGetValue((MethodDefinitionHandle)lf.MetadataToken, out var name))
+					var proposedNameForStoresFromNewObj = variable.StoreInstructions.OfType<StLoc>()
+						.Select(expr => GetNameByType(GuessType(variable.Type, expr.Value, context)))
+						.Except(currentLowerCaseTypeOrMemberNames).ToList();
+					if (proposedNameForStoresFromNewObj.Count == 1)
 					{
-						lf.Name = name;
+						proposedName = proposedNameForStoresFromNewObj[0];
 					}
 				}
+				if (string.IsNullOrEmpty(proposedName))
+				{
+					proposedName = GetNameByType(variable.Type);
+				}
+
+				// for generated names remove number-suffixes
+				return SplitName(proposedName, out _);
 			}
 		}
 
-		string AssignName(ILVariable v, Dictionary<ILVariable, string> variableMapping)
+		public void Run(ILFunction function, ILTransformContext context)
+		{
+			this.context = context;
+			function.AcceptVisitor(this, null);
+		}
+
+		protected override Unit Default(ILInstruction inst, VariableScope context)
 		{
-			// variable has no valid name
-			string newName = v.Name;
-			if (v.HasGeneratedName || !IsValidName(newName))
+			foreach (var child in inst.Children)
 			{
-				// don't use the name from the debug symbols if it looks like a generated name
-				// generate a new one based on how the variable is used
-				newName = GenerateNameForVariable(v);
+				child.AcceptVisitor(this, context);
 			}
-			// use the existing name and update index appended to future conflicts
-			string nameWithoutNumber = SplitName(newName, out int newIndex);
-			if (reservedVariableNames.TryGetValue(nameWithoutNumber, out int lastUsedIndex))
+
+			if (inst is not IInstructionWithVariableOperand { Variable: var v })
+				return default;
+
+			// if there is already a valid name for the variable slot, just use it
+			string name = context.TryGetExistingName(v);
+			if (!string.IsNullOrEmpty(name))
 			{
-				if (v.Type.IsKnownType(KnownTypeCode.Int32) && loopCounters.Contains(v))
-				{
-					// special case for loop counters,
-					// we don't want them to be named i, i2, ..., but i, j, ...
-					newName = GenerateNameForVariable(v);
-					nameWithoutNumber = newName;
-					newIndex = 1;
-				}
+				v.Name = name;
+				return default;
 			}
-			if (reservedVariableNames.TryGetValue(nameWithoutNumber, out lastUsedIndex))
+
+			switch (v.Kind)
 			{
-				// name without number was already used
-				if (newIndex > lastUsedIndex)
-				{
-					// new index is larger than last, so we can use it
-				}
-				else
+				case VariableKind.Parameter when !v.HasGeneratedName && v.Function.Kind == ILFunctionKind.TopLevelFunction:
+					// Parameter names of top-level functions are handled in ILReader.CreateILVariable
+					// and CSharpDecompiler.FixParameterNames
+					break;
+				case VariableKind.InitializerTarget: // keep generated names
+				case VariableKind.NamedArgument:
+					context.ReserveVariableName(v.Name);
+					break;
+				case VariableKind.DisplayClassLocal:
+					v.Name = context.NextDisplayClassLocal();
+					break;
+				case VariableKind.Local when v.Index != null:
+					name = context.TryGetExistingName(v.Function, v.Index.Value);
+					if (name != null)
+					{
+						// make sure all local ILVariables that refer to the same slot in the locals signature
+						// are assigned the same name.
+						v.Name = name;
+					}
+					else
+					{
+						v.Name = context.AssignName(v);
+						context.AssignNameToLocalSignatureIndex(v.Function, v.Index.Value, v.Name);
+					}
+					break;
+				default:
+					v.Name = context.AssignName(v);
+					break;
+			}
+
+			return default;
+		}
+
+		protected internal override Unit VisitILFunction(ILFunction function, VariableScope context)
+		{
+			if (function.Kind == ILFunctionKind.LocalFunction)
+			{
+				// assign names to local functions
+				if (!LocalFunctionDecompiler.ParseLocalFunctionName(function.Name, out _, out var newName) || !IsValidName(newName))
+					newName = null;
+				if (newName == null)
 				{
-					// new index is smaller or equal, so we use the next value
-					newIndex = lastUsedIndex + 1;
+					string nameWithoutNumber = "f";
+					if (!context.IsReservedVariableName(nameWithoutNumber, out int currentIndex))
+					{
+						currentIndex = 1;
+					}
+					int count = Math.Max(1, currentIndex) + 1;
+					context.ReserveVariableName(nameWithoutNumber, count);
+					if (count > 1)
+					{
+						newName = nameWithoutNumber + count.ToString();
+					}
+					else
+					{
+						newName = nameWithoutNumber;
+					}
 				}
-				// resolve conflicts by appending the index to the new name:
-				newName = nameWithoutNumber + newIndex.ToString();
+				function.Name = newName;
+				function.ReducedMethod.Name = newName;
+				context.Add((MethodDefinitionHandle)function.ReducedMethod.MetadataToken, newName);
+			}
+
+			return base.VisitILFunction(function, new VariableScope(function, this.context, context));
+		}
+
+		protected internal override Unit VisitCall(Call inst, VariableScope context)
+		{
+			if (inst.Method is LocalFunctionMethod m)
+			{
+				string name = context.TryGetExistingName((MethodDefinitionHandle)m.MetadataToken);
+				if (!string.IsNullOrEmpty(name))
+					m.Name = name;
 			}
-			// update the last used index
-			reservedVariableNames[nameWithoutNumber] = newIndex;
-			variableMapping.Add(v, newName);
-			return newName;
+
+			return base.VisitCall(inst, context);
+		}
+
+		protected internal override Unit VisitLdFtn(LdFtn inst, VariableScope context)
+		{
+			if (inst.Method is LocalFunctionMethod m)
+			{
+				string name = context.TryGetExistingName((MethodDefinitionHandle)m.MetadataToken);
+				if (!string.IsNullOrEmpty(name))
+					m.Name = name;
+			}
+
+			return base.VisitLdFtn(inst, context);
+		}
+
+		static IEnumerable<string> CollectAllLowerCaseMemberNames(ITypeDefinition type)
+		{
+			foreach (var item in type.GetMembers(m => IsLowerCase(m.Name)))
+				yield return item.Name;
+		}
+
+		static IEnumerable<string> CollectAllLowerCaseTypeNames(ITypeDefinition type)
+		{
+			var ns = type.ParentModule.Compilation.GetNamespaceByFullName(type.Namespace);
+			foreach (var item in ns.Types)
+			{
+				if (IsLowerCase(item.Name))
+					yield return item.Name;
+			}
+		}
+
+		static bool IsLowerCase(string name)
+		{
+			return name.Length > 0 && char.ToLower(name[0]) == name[0];
 		}
 
 		/// <remarks>
@@ -351,118 +595,6 @@ namespace ICSharpCode.Decompiler.IL.Transforms
 			return true;
 		}
 
-		HashSet<ILVariable> CollectLoopCounters(ILFunction function)
-		{
-			var loopCounters = new HashSet<ILVariable>();
-
-			foreach (BlockContainer possibleLoop in function.Descendants.OfType<BlockContainer>())
-			{
-				if (possibleLoop.Kind != ContainerKind.For)
-					continue;
-				foreach (var inst in possibleLoop.Blocks.Last().Instructions)
-				{
-					if (HighLevelLoopTransform.MatchIncrement(inst, out var variable))
-						loopCounters.Add(variable);
-				}
-			}
-
-			return loopCounters;
-		}
-
-		string GenerateNameForVariable(ILVariable variable)
-		{
-			string proposedName = null;
-			if (variable.Type.IsKnownType(KnownTypeCode.Int32))
-			{
-				// test whether the variable might be a loop counter
-				if (loopCounters.Contains(variable))
-				{
-					// For loop variables, use i,j,k,l,m,n
-					for (char c = 'i'; c <= maxLoopVariableName; c++)
-					{
-						if (!reservedVariableNames.ContainsKey(c.ToString()))
-						{
-							proposedName = c.ToString();
-							break;
-						}
-					}
-				}
-			}
-			// The ComponentResourceManager inside InitializeComponent must be named "resources",
-			// otherwise the WinForms designer won't load the Form.
-			if (CSharp.CSharpDecompiler.IsWindowsFormsInitializeComponentMethod(context.Function.Method) && variable.Type.FullName == "System.ComponentModel.ComponentResourceManager")
-			{
-				proposedName = "resources";
-			}
-			if (string.IsNullOrEmpty(proposedName))
-			{
-				var proposedNameForAddress = variable.AddressInstructions.OfType<LdLoca>()
-					.Select(arg => arg.Parent is CallInstruction c ? c.GetParameter(arg.ChildIndex)?.Name : null)
-					.Where(arg => !string.IsNullOrWhiteSpace(arg))
-					.Except(currentLowerCaseTypeOrMemberNames).ToList();
-				if (proposedNameForAddress.Count > 0)
-				{
-					proposedName = proposedNameForAddress[0];
-				}
-			}
-			if (string.IsNullOrEmpty(proposedName))
-			{
-				var proposedNameForStores = new HashSet<string>();
-				foreach (var store in variable.StoreInstructions)
-				{
-					if (store is StLoc stloc)
-					{
-						var name = GetNameFromInstruction(stloc.Value);
-						if (!currentLowerCaseTypeOrMemberNames.Contains(name))
-							proposedNameForStores.Add(name);
-					}
-					else if (store is MatchInstruction match && match.SlotInfo == MatchInstruction.SubPatternsSlot)
-					{
-						var name = GetNameFromInstruction(match.TestedOperand);
-						if (!currentLowerCaseTypeOrMemberNames.Contains(name))
-							proposedNameForStores.Add(name);
-					}
-					else if (store is PinnedRegion pinnedRegion)
-					{
-						var name = GetNameFromInstruction(pinnedRegion.Init);
-						if (!currentLowerCaseTypeOrMemberNames.Contains(name))
-							proposedNameForStores.Add(name);
-					}
-				}
-				if (proposedNameForStores.Count == 1)
-				{
-					proposedName = proposedNameForStores.Single();
-				}
-			}
-			if (string.IsNullOrEmpty(proposedName))
-			{
-				var proposedNameForLoads = variable.LoadInstructions
-					.Select(arg => GetNameForArgument(arg.Parent, arg.ChildIndex))
-					.Except(currentLowerCaseTypeOrMemberNames).ToList();
-				if (proposedNameForLoads.Count == 1)
-				{
-					proposedName = proposedNameForLoads[0];
-				}
-			}
-			if (string.IsNullOrEmpty(proposedName) && variable.Kind == VariableKind.StackSlot)
-			{
-				var proposedNameForStoresFromNewObj = variable.StoreInstructions.OfType<StLoc>()
-					.Select(expr => GetNameByType(GuessType(variable.Type, expr.Value, context)))
-					.Except(currentLowerCaseTypeOrMemberNames).ToList();
-				if (proposedNameForStoresFromNewObj.Count == 1)
-				{
-					proposedName = proposedNameForStoresFromNewObj[0];
-				}
-			}
-			if (string.IsNullOrEmpty(proposedName))
-			{
-				proposedName = GetNameByType(variable.Type);
-			}
-
-			// for generated names remove number-suffixes
-			return SplitName(proposedName, out _);
-		}
-
 		static string GetNameFromInstruction(ILInstruction inst)
 		{
 			switch (inst)
@@ -663,7 +795,7 @@ namespace ICSharpCode.Decompiler.IL.Transforms
 			return lowerCaseName;
 		}
 
-		internal static IType GuessType(IType variableType, ILInstruction inst, ILTransformContext context)
+		static IType GuessType(IType variableType, ILInstruction inst, ILTransformContext context)
 		{
 			if (!variableType.IsKnownType(KnownTypeCode.Object))
 				return variableType;
diff --git a/ICSharpCode.Decompiler/Util/CollectionExtensions.cs b/ICSharpCode.Decompiler/Util/CollectionExtensions.cs
index 0d5febe9c..a88ac4376 100644
--- a/ICSharpCode.Decompiler/Util/CollectionExtensions.cs
+++ b/ICSharpCode.Decompiler/Util/CollectionExtensions.cs
@@ -225,7 +225,7 @@ namespace ICSharpCode.Decompiler.Util
 				yield return func(index++, element);
 		}
 
-		public static IEnumerable<(int, T)> WithIndex<T>(this ICollection<T> source)
+		public static IEnumerable<(int, T)> WithIndex<T>(this IEnumerable<T> source)
 		{
 			int index = 0;
 			foreach (var item in source)

From 0481c7d1ee05f7b6008cc00564cc603c099ce3de Mon Sep 17 00:00:00 2001
From: Siegfried Pammer <siegfriedpammer@gmail.com>
Date: Sun, 2 Mar 2025 11:33:28 +0100
Subject: [PATCH 40/49] Improve LINQ decompiler to support combining lambda
 parameter names if they syntactically refer to the same range variable

---
 .../Transforms/CombineQueryExpressions.cs     | 32 ++++++++-----------
 .../Transforms/IntroduceQueryExpressions.cs   | 21 ++++++++++--
 2 files changed, 31 insertions(+), 22 deletions(-)

diff --git a/ICSharpCode.Decompiler/CSharp/Transforms/CombineQueryExpressions.cs b/ICSharpCode.Decompiler/CSharp/Transforms/CombineQueryExpressions.cs
index ad92d1a27..55d6dec35 100644
--- a/ICSharpCode.Decompiler/CSharp/Transforms/CombineQueryExpressions.cs
+++ b/ICSharpCode.Decompiler/CSharp/Transforms/CombineQueryExpressions.cs
@@ -16,12 +16,12 @@
 // OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
 // DEALINGS IN THE SOFTWARE.
 
-using System;
 using System.Collections.Generic;
 using System.Linq;
 
 using ICSharpCode.Decompiler.CSharp.Syntax;
 using ICSharpCode.Decompiler.CSharp.Syntax.PatternMatching;
+using ICSharpCode.Decompiler.Util;
 
 namespace ICSharpCode.Decompiler.CSharp.Transforms
 {
@@ -54,12 +54,10 @@ namespace ICSharpCode.Decompiler.CSharp.Transforms
 				next = child.NextSibling;
 				CombineQueries(child, fromOrLetIdentifiers);
 			}
-			QueryExpression query = node as QueryExpression;
-			if (query != null)
+			if (node is QueryExpression query)
 			{
 				QueryFromClause fromClause = (QueryFromClause)query.Clauses.First();
-				QueryExpression innerQuery = fromClause.Expression as QueryExpression;
-				if (innerQuery != null)
+				if (fromClause.Expression is QueryExpression innerQuery)
 				{
 					if (TryRemoveTransparentIdentifier(query, fromClause, innerQuery, fromOrLetIdentifiers))
 					{
@@ -165,21 +163,17 @@ namespace ICSharpCode.Decompiler.CSharp.Transforms
 			{
 				RemoveTransparentIdentifierReferences(child, fromOrLetIdentifiers);
 			}
-			MemberReferenceExpression mre = node as MemberReferenceExpression;
-			if (mre != null)
+			if (node is MemberReferenceExpression mre && mre.Target is IdentifierExpression ident
+				&& CSharpDecompiler.IsTransparentIdentifier(ident.Identifier))
 			{
-				IdentifierExpression ident = mre.Target as IdentifierExpression;
-				if (ident != null && CSharpDecompiler.IsTransparentIdentifier(ident.Identifier))
-				{
-					IdentifierExpression newIdent = new IdentifierExpression(mre.MemberName);
-					mre.TypeArguments.MoveTo(newIdent.TypeArguments);
-					newIdent.CopyAnnotationsFrom(mre);
-					newIdent.RemoveAnnotations<Semantics.MemberResolveResult>(); // remove the reference to the property of the anonymous type
-					if (fromOrLetIdentifiers.TryGetValue(mre.MemberName, out var annotation))
-						newIdent.AddAnnotation(annotation);
-					mre.ReplaceWith(newIdent);
-					return;
-				}
+				IdentifierExpression newIdent = new IdentifierExpression(mre.MemberName);
+				mre.TypeArguments.MoveTo(newIdent.TypeArguments);
+				newIdent.CopyAnnotationsFrom(mre);
+				newIdent.RemoveAnnotations<Semantics.MemberResolveResult>(); // remove the reference to the property of the anonymous type
+				if (fromOrLetIdentifiers.TryGetValue(mre.MemberName, out var annotation))
+					newIdent.AddAnnotation(annotation);
+				mre.ReplaceWith(newIdent);
+				return;
 			}
 		}
 	}
diff --git a/ICSharpCode.Decompiler/CSharp/Transforms/IntroduceQueryExpressions.cs b/ICSharpCode.Decompiler/CSharp/Transforms/IntroduceQueryExpressions.cs
index 8e5e55368..1590b2ab3 100644
--- a/ICSharpCode.Decompiler/CSharp/Transforms/IntroduceQueryExpressions.cs
+++ b/ICSharpCode.Decompiler/CSharp/Transforms/IntroduceQueryExpressions.cs
@@ -17,10 +17,10 @@
 // DEALINGS IN THE SOFTWARE.
 
 using System;
-using System.Diagnostics;
 using System.Linq;
 
 using ICSharpCode.Decompiler.CSharp.Syntax;
+using ICSharpCode.Decompiler.IL;
 
 namespace ICSharpCode.Decompiler.CSharp.Transforms
 {
@@ -54,13 +54,14 @@ namespace ICSharpCode.Decompiler.CSharp.Transforms
 				while (IsDegenerateQuery(innerQuery))
 				{
 					QueryFromClause innerFromClause = (QueryFromClause)innerQuery.Clauses.First();
-					if (fromClause.Identifier != innerFromClause.Identifier)
-						break;
+					ILVariable innerVariable = innerFromClause.Annotation<ILVariableResolveResult>()?.Variable;
+					ILVariable rangeVariable = fromClause.Annotation<ILVariableResolveResult>()?.Variable;
 					// Replace the fromClause with all clauses from the inner query
 					fromClause.Remove();
 					QueryClause insertionPos = null;
 					foreach (var clause in innerQuery.Clauses)
 					{
+						CombineRangeVariables(clause, innerVariable, rangeVariable);
 						query.Clauses.InsertAfter(insertionPos, insertionPos = clause.Detach());
 					}
 					fromClause = innerFromClause;
@@ -69,6 +70,20 @@ namespace ICSharpCode.Decompiler.CSharp.Transforms
 			}
 		}
 
+		private void CombineRangeVariables(QueryClause clause, ILVariable oldVariable, ILVariable newVariable)
+		{
+			foreach (var identifier in clause.DescendantNodes().OfType<Identifier>())
+			{
+				var variable = identifier.Parent.Annotation<ILVariableResolveResult>()?.Variable;
+				if (variable == oldVariable)
+				{
+					identifier.Parent.RemoveAnnotations<ILVariableResolveResult>();
+					identifier.Parent.AddAnnotation(new ILVariableResolveResult(newVariable));
+					identifier.ReplaceWith(Identifier.Create(newVariable.Name));
+				}
+			}
+		}
+
 		bool IsDegenerateQuery(QueryExpression query)
 		{
 			if (query == null)

From ffcd468d22f492bbdcc4c4045a2a74df4a7bf308 Mon Sep 17 00:00:00 2001
From: Siegfried Pammer <siegfriedpammer@gmail.com>
Date: Sun, 2 Mar 2025 21:41:20 +0100
Subject: [PATCH 41/49] Fix #1572: parameters of lambdas and local functions
 are renamed, if there are with names from outer scopes collisions.

---
 .../PrettyTestRunner.cs                       |  56 ++++-----
 .../TestCases/ILPretty/GuessAccessors.cs      |   4 +-
 .../TestCases/Pretty/DelegateConstruction.cs  |  33 ++++++
 .../TestCases/VBPretty/Async.cs               |  12 +-
 .../CSharp/CSharpDecompiler.cs                |   2 +-
 .../CSharp/ExpressionBuilder.cs               |   2 +-
 .../IL/Transforms/AssignVariableNames.cs      | 112 +++++++++++-------
 7 files changed, 143 insertions(+), 78 deletions(-)

diff --git a/ICSharpCode.Decompiler.Tests/PrettyTestRunner.cs b/ICSharpCode.Decompiler.Tests/PrettyTestRunner.cs
index f4ff7c914..cfe0c420b 100644
--- a/ICSharpCode.Decompiler.Tests/PrettyTestRunner.cs
+++ b/ICSharpCode.Decompiler.Tests/PrettyTestRunner.cs
@@ -233,22 +233,21 @@ namespace ICSharpCode.Decompiler.Tests
 		[Test]
 		public async Task ExceptionHandling([ValueSource(nameof(defaultOptions))] CompilerOptions cscOptions)
 		{
-			await RunForLibrary(cscOptions: cscOptions, decompilerSettings: new DecompilerSettings {
-				NullPropagation = false,
+			await RunForLibrary(cscOptions: cscOptions, configureDecompiler: settings => {
+				settings.NullPropagation = false;
 				// legacy csc generates a dead store in debug builds
-				RemoveDeadStores = (cscOptions == CompilerOptions.None),
-				FileScopedNamespaces = false,
+				settings.RemoveDeadStores = (cscOptions == CompilerOptions.None);
+				settings.FileScopedNamespaces = false;
 			});
 		}
 
 		[Test]
 		public async Task Switch([ValueSource(nameof(defaultOptions))] CompilerOptions cscOptions)
 		{
-			await RunForLibrary(cscOptions: cscOptions, decompilerSettings: new DecompilerSettings {
+			await RunForLibrary(cscOptions: cscOptions, configureDecompiler: settings => {
 				// legacy csc generates a dead store in debug builds
-				RemoveDeadStores = (cscOptions == CompilerOptions.None),
-				SwitchExpressions = false,
-				FileScopedNamespaces = false,
+				settings.RemoveDeadStores = (cscOptions == CompilerOptions.None);
+				settings.SwitchExpressions = false;
 			});
 		}
 
@@ -267,7 +266,10 @@ namespace ICSharpCode.Decompiler.Tests
 		[Test]
 		public async Task DelegateConstruction([ValueSource(nameof(defaultOptionsWithMcs))] CompilerOptions cscOptions)
 		{
-			await RunForLibrary(cscOptions: cscOptions);
+			await RunForLibrary(cscOptions: cscOptions, configureDecompiler: settings => {
+				settings.QueryExpressions = false;
+				settings.NullableReferenceTypes = false;
+			});
 		}
 
 		[Test]
@@ -293,9 +295,9 @@ namespace ICSharpCode.Decompiler.Tests
 		{
 			await RunForLibrary(
 				cscOptions: cscOptions,
-				decompilerSettings: new DecompilerSettings {
-					UseEnhancedUsing = false,
-					FileScopedNamespaces = false,
+				configureDecompiler: settings => {
+					settings.UseEnhancedUsing = false;
+					settings.FileScopedNamespaces = false;
 				}
 			);
 		}
@@ -327,11 +329,11 @@ namespace ICSharpCode.Decompiler.Tests
 		[Test]
 		public async Task Loops([ValueSource(nameof(defaultOptionsWithMcs))] CompilerOptions cscOptions)
 		{
-			DecompilerSettings settings = Tester.GetSettings(cscOptions);
-			// legacy csc generates a dead store in debug builds
-			settings.RemoveDeadStores = (cscOptions == CompilerOptions.None);
-			settings.UseExpressionBodyForCalculatedGetterOnlyProperties = false;
-			await RunForLibrary(cscOptions: cscOptions, decompilerSettings: settings);
+			await RunForLibrary(cscOptions: cscOptions, configureDecompiler: settings => {
+				// legacy csc generates a dead store in debug builds
+				settings.RemoveDeadStores = (cscOptions == CompilerOptions.None);
+				settings.UseExpressionBodyForCalculatedGetterOnlyProperties = false;
+			});
 		}
 
 		[Test]
@@ -440,9 +442,7 @@ namespace ICSharpCode.Decompiler.Tests
 		[Test]
 		public async Task VariableNamingWithoutSymbols([ValueSource(nameof(defaultOptions))] CompilerOptions cscOptions)
 		{
-			var settings = Tester.GetSettings(cscOptions);
-			settings.UseDebugSymbols = false;
-			await RunForLibrary(cscOptions: cscOptions, decompilerSettings: settings);
+			await RunForLibrary(cscOptions: cscOptions, configureDecompiler: settings => settings.UseDebugSymbols = false);
 		}
 
 		[Test]
@@ -474,7 +474,7 @@ namespace ICSharpCode.Decompiler.Tests
 		{
 			await RunForLibrary(
 				cscOptions: cscOptions,
-				decompilerSettings: new DecompilerSettings { UseEnhancedUsing = false, FileScopedNamespaces = false }
+				configureDecompiler: settings => { settings.UseEnhancedUsing = false; }
 			);
 		}
 
@@ -499,7 +499,7 @@ namespace ICSharpCode.Decompiler.Tests
 		[Test]
 		public async Task FileScopedNamespaces([ValueSource(nameof(roslyn4OrNewerOptions))] CompilerOptions cscOptions)
 		{
-			await RunForLibrary(cscOptions: cscOptions, decompilerSettings: new DecompilerSettings());
+			await RunForLibrary(cscOptions: cscOptions, configureDecompiler: settings => settings.FileScopedNamespaces = true);
 		}
 
 		[Test]
@@ -601,7 +601,7 @@ namespace ICSharpCode.Decompiler.Tests
 		[Test]
 		public async Task Issue1080([ValueSource(nameof(roslynOnlyOptions))] CompilerOptions cscOptions)
 		{
-			await RunForLibrary(cscOptions: cscOptions, decompilerSettings: new DecompilerSettings(CSharp.LanguageVersion.CSharp6));
+			await RunForLibrary(cscOptions: cscOptions, configureDecompiler: settings => settings.SetLanguageVersion(CSharp.LanguageVersion.CSharp6));
 		}
 
 		[Test]
@@ -712,12 +712,12 @@ namespace ICSharpCode.Decompiler.Tests
 			await RunForLibrary(cscOptions: cscOptions);
 		}
 
-		async Task RunForLibrary([CallerMemberName] string testName = null, AssemblerOptions asmOptions = AssemblerOptions.None, CompilerOptions cscOptions = CompilerOptions.None, DecompilerSettings decompilerSettings = null)
+		async Task RunForLibrary([CallerMemberName] string testName = null, AssemblerOptions asmOptions = AssemblerOptions.None, CompilerOptions cscOptions = CompilerOptions.None, Action<DecompilerSettings> configureDecompiler = null)
 		{
-			await Run(testName, asmOptions | AssemblerOptions.Library, cscOptions | CompilerOptions.Library, decompilerSettings);
+			await Run(testName, asmOptions | AssemblerOptions.Library, cscOptions | CompilerOptions.Library, configureDecompiler);
 		}
 
-		async Task Run([CallerMemberName] string testName = null, AssemblerOptions asmOptions = AssemblerOptions.None, CompilerOptions cscOptions = CompilerOptions.None, DecompilerSettings decompilerSettings = null)
+		async Task Run([CallerMemberName] string testName = null, AssemblerOptions asmOptions = AssemblerOptions.None, CompilerOptions cscOptions = CompilerOptions.None, Action<DecompilerSettings> configureDecompiler = null)
 		{
 			var csFile = Path.Combine(TestCasePath, testName + ".cs");
 			var exeFile = TestsAssemblyOutput.GetFilePath(TestCasePath, testName, Tester.GetSuffix(cscOptions) + ".exe");
@@ -739,7 +739,9 @@ namespace ICSharpCode.Decompiler.Tests
 			}
 
 			// 2. Decompile
-			var decompiled = await Tester.DecompileCSharp(exeFile, decompilerSettings ?? Tester.GetSettings(cscOptions)).ConfigureAwait(false);
+			var settings = Tester.GetSettings(cscOptions);
+			configureDecompiler?.Invoke(settings);
+			var decompiled = await Tester.DecompileCSharp(exeFile, settings).ConfigureAwait(false);
 
 			// 3. Compile
 			CodeAssert.FilesAreEqual(csFile, decompiled, Tester.GetPreprocessorSymbols(cscOptions).Append("EXPECTED_OUTPUT").ToArray());
diff --git a/ICSharpCode.Decompiler.Tests/TestCases/ILPretty/GuessAccessors.cs b/ICSharpCode.Decompiler.Tests/TestCases/ILPretty/GuessAccessors.cs
index c9e0f56e0..423ad4119 100644
--- a/ICSharpCode.Decompiler.Tests/TestCases/ILPretty/GuessAccessors.cs
+++ b/ICSharpCode.Decompiler.Tests/TestCases/ILPretty/GuessAccessors.cs
@@ -15,8 +15,8 @@ namespace ClassLibrary1
 			//IL_0007: Expected O, but got Unknown
 			UnknownClass val = new UnknownClass();
 			int? unknownProperty = val.UnknownProperty;
-			int? num2 = (val.UnknownProperty = unknownProperty.GetValueOrDefault());
-			int? num3 = num2;
+			int? num = (val.UnknownProperty = unknownProperty.GetValueOrDefault());
+			int? num3 = num;
 			List<object> list = new List<object> {
 			val[unknownProperty.Value] ?? "",
 			val.NotProperty,
diff --git a/ICSharpCode.Decompiler.Tests/TestCases/Pretty/DelegateConstruction.cs b/ICSharpCode.Decompiler.Tests/TestCases/Pretty/DelegateConstruction.cs
index fd76c9e23..d7566dced 100644
--- a/ICSharpCode.Decompiler.Tests/TestCases/Pretty/DelegateConstruction.cs
+++ b/ICSharpCode.Decompiler.Tests/TestCases/Pretty/DelegateConstruction.cs
@@ -19,15 +19,33 @@
 using System;
 using System.Collections.Generic;
 using System.Linq;
+using System.Runtime.CompilerServices;
 using System.Threading;
 #if CS100
 using System.Threading.Tasks;
+
 #endif
 
 namespace ICSharpCode.Decompiler.Tests.TestCases.Pretty.DelegateConstruction
 {
 	public static class DelegateConstruction
 	{
+		internal class Dummy
+		{
+			public int baz;
+
+			public List<Dummy> more;
+		}
+
+		[CompilerGenerated]
+		internal class Helper
+		{
+			internal bool HelpMe(Dummy dum)
+			{
+				return true;
+			}
+		}
+
 		private class InstanceTests
 		{
 			public struct SomeData
@@ -643,6 +661,21 @@ namespace ICSharpCode.Decompiler.Tests.TestCases.Pretty.DelegateConstruction
 		{
 			del(x);
 		}
+
+		public void Issue1572(DelegateConstruction.Dummy dum)
+		{
+#if EXPECTED_OUTPUT
+			DelegateConstruction.Helper CS_0024_003C_003E8__locals0 = new DelegateConstruction.Helper();
+			DelegateConstruction.Dummy dummy = dum.more.Where((DelegateConstruction.Dummy dummy2) => true).Where((DelegateConstruction.Dummy dummy2) => true).FirstOrDefault();
+			Console.WriteLine();
+			dummy.baz++;
+#else
+			DelegateConstruction.Helper h = new DelegateConstruction.Helper();
+			DelegateConstruction.Dummy localDummy = dum.more.Where(h.HelpMe).Where(h.HelpMe).FirstOrDefault();
+			Console.WriteLine();
+			localDummy.baz++;
+#endif
+		}
 	}
 
 	[AttributeUsage(AttributeTargets.All)]
diff --git a/ICSharpCode.Decompiler.Tests/TestCases/VBPretty/Async.cs b/ICSharpCode.Decompiler.Tests/TestCases/VBPretty/Async.cs
index 6cb502dd0..7c2c76025 100644
--- a/ICSharpCode.Decompiler.Tests/TestCases/VBPretty/Async.cs
+++ b/ICSharpCode.Decompiler.Tests/TestCases/VBPretty/Async.cs
@@ -40,9 +40,9 @@ namespace ICSharpCode.Decompiler.Tests.TestCases.VBPretty
 		public async void AwaitDefaultYieldAwaitable()
 		{
 #if LEGACY_VBC || (OPTIMIZE && !ROSLYN4)
-			YieldAwaitable yieldAwaitable = default(YieldAwaitable);
-			YieldAwaitable yieldAwaitable2 = yieldAwaitable;
-			await yieldAwaitable2;
+			YieldAwaitable yieldAwaitable2 = default(YieldAwaitable);
+			YieldAwaitable yieldAwaitable = yieldAwaitable2;
+			await yieldAwaitable;
 #else
 			await default(YieldAwaitable);
 #endif
@@ -51,9 +51,9 @@ namespace ICSharpCode.Decompiler.Tests.TestCases.VBPretty
 		public async void AwaitDefaultHopToThreadPool()
 		{
 #if LEGACY_VBC || (OPTIMIZE && !ROSLYN4)
-			HopToThreadPoolAwaitable hopToThreadPoolAwaitable = default(HopToThreadPoolAwaitable);
-			HopToThreadPoolAwaitable hopToThreadPoolAwaitable2 = hopToThreadPoolAwaitable;
-			await hopToThreadPoolAwaitable2;
+			HopToThreadPoolAwaitable hopToThreadPoolAwaitable2 = default(HopToThreadPoolAwaitable);
+			HopToThreadPoolAwaitable hopToThreadPoolAwaitable = hopToThreadPoolAwaitable2;
+			await hopToThreadPoolAwaitable;
 #else
 			await default(HopToThreadPoolAwaitable);
 #endif
diff --git a/ICSharpCode.Decompiler/CSharp/CSharpDecompiler.cs b/ICSharpCode.Decompiler/CSharp/CSharpDecompiler.cs
index 86f6ed66e..b9559dba1 100644
--- a/ICSharpCode.Decompiler/CSharp/CSharpDecompiler.cs
+++ b/ICSharpCode.Decompiler/CSharp/CSharpDecompiler.cs
@@ -1282,7 +1282,7 @@ namespace ICSharpCode.Decompiler.CSharp
 			{
 				if (string.IsNullOrWhiteSpace(parameter.Name) && !parameter.Type.IsArgList())
 				{
-					// needs to be consistent with logic in ILReader.CreateILVarable
+					// needs to be consistent with logic in ILReader.CreateILVariable
 					parameter.Name = "P_" + i;
 				}
 				i++;
diff --git a/ICSharpCode.Decompiler/CSharp/ExpressionBuilder.cs b/ICSharpCode.Decompiler/CSharp/ExpressionBuilder.cs
index e95134ed4..90df1890a 100644
--- a/ICSharpCode.Decompiler/CSharp/ExpressionBuilder.cs
+++ b/ICSharpCode.Decompiler/CSharp/ExpressionBuilder.cs
@@ -2554,7 +2554,7 @@ namespace ICSharpCode.Decompiler.CSharp
 				}
 				if (string.IsNullOrEmpty(pd.Name) && !pd.Type.IsArgList())
 				{
-					// needs to be consistent with logic in ILReader.CreateILVarable(ParameterDefinition)
+					// needs to be consistent with logic in ILReader.CreateILVariable
 					pd.Name = "P_" + i;
 				}
 				if (settings.AnonymousTypes && parameter.Type.ContainsAnonymousType())
diff --git a/ICSharpCode.Decompiler/IL/Transforms/AssignVariableNames.cs b/ICSharpCode.Decompiler/IL/Transforms/AssignVariableNames.cs
index 0d1acd261..24f7079fc 100644
--- a/ICSharpCode.Decompiler/IL/Transforms/AssignVariableNames.cs
+++ b/ICSharpCode.Decompiler/IL/Transforms/AssignVariableNames.cs
@@ -179,7 +179,7 @@ namespace ICSharpCode.Decompiler.IL.Transforms
 							string name = p.Name;
 							if (string.IsNullOrWhiteSpace(name) && p.Type != SpecialType.ArgList)
 							{
-								// needs to be consistent with logic in ILReader.CreateILVarable
+								// needs to be consistent with logic in ILReader.CreateILVariable
 								name = "P_" + i;
 							}
 							if (variables.TryGetValue(i, out var v))
@@ -221,6 +221,10 @@ namespace ICSharpCode.Decompiler.IL.Transforms
 							if (variables.TryGetValue(i, out var v))
 								variableMapping[v] = p.Name;
 						}
+						else if (variables.TryGetValue(i, out var v))
+						{
+							v.HasGeneratedName = true;
+						}
 					}
 				}
 			}
@@ -287,6 +291,13 @@ namespace ICSharpCode.Decompiler.IL.Transforms
 				return loopCounters.Contains(v) || (parentScope?.IsLoopCounter(v) == true);
 			}
 
+			public string AssignNameIfUnassigned(ILVariable v)
+			{
+				if (variableMapping.TryGetValue(v, out var name))
+					return name;
+				return AssignName(v);
+			}
+
 			public string AssignName(ILVariable v)
 			{
 				// variable has no valid name
@@ -432,57 +443,65 @@ namespace ICSharpCode.Decompiler.IL.Transforms
 			function.AcceptVisitor(this, null);
 		}
 
-		protected override Unit Default(ILInstruction inst, VariableScope context)
+		Unit VisitChildren(ILInstruction inst, VariableScope context)
 		{
 			foreach (var child in inst.Children)
 			{
 				child.AcceptVisitor(this, context);
 			}
 
-			if (inst is not IInstructionWithVariableOperand { Variable: var v })
-				return default;
+			return default;
+		}
 
-			// if there is already a valid name for the variable slot, just use it
-			string name = context.TryGetExistingName(v);
-			if (!string.IsNullOrEmpty(name))
+		protected override Unit Default(ILInstruction inst, VariableScope context)
+		{
+			if (inst is IInstructionWithVariableOperand { Variable: var v })
 			{
-				v.Name = name;
-				return default;
-			}
+				// if there is already a valid name for the variable slot, just use it
+				string name = context.TryGetExistingName(v);
+				if (!string.IsNullOrEmpty(name))
+				{
+					v.Name = name;
+					return VisitChildren(inst, context);
+				}
 
-			switch (v.Kind)
-			{
-				case VariableKind.Parameter when !v.HasGeneratedName && v.Function.Kind == ILFunctionKind.TopLevelFunction:
-					// Parameter names of top-level functions are handled in ILReader.CreateILVariable
-					// and CSharpDecompiler.FixParameterNames
-					break;
-				case VariableKind.InitializerTarget: // keep generated names
-				case VariableKind.NamedArgument:
-					context.ReserveVariableName(v.Name);
-					break;
-				case VariableKind.DisplayClassLocal:
-					v.Name = context.NextDisplayClassLocal();
-					break;
-				case VariableKind.Local when v.Index != null:
-					name = context.TryGetExistingName(v.Function, v.Index.Value);
-					if (name != null)
-					{
-						// make sure all local ILVariables that refer to the same slot in the locals signature
-						// are assigned the same name.
-						v.Name = name;
-					}
-					else
-					{
+				switch (v.Kind)
+				{
+					case VariableKind.Parameter when !v.HasGeneratedName && v.Function.Kind == ILFunctionKind.TopLevelFunction:
+						// Parameter names of top-level functions are handled in ILReader.CreateILVariable
+						// and CSharpDecompiler.FixParameterNames
+						break;
+					case VariableKind.InitializerTarget: // keep generated names
+					case VariableKind.NamedArgument:
+						context.ReserveVariableName(v.Name);
+						break;
+					case VariableKind.UsingLocal when v.AddressCount == 0 && v.LoadCount == 0:
+						// using variables that are not read, will not be declared in source source
+						break;
+					case VariableKind.DisplayClassLocal:
+						v.Name = context.NextDisplayClassLocal();
+						break;
+					case VariableKind.Local when v.Index != null:
+						name = context.TryGetExistingName(v.Function, v.Index.Value);
+						if (name != null)
+						{
+							// make sure all local ILVariables that refer to the same slot in the locals signature
+							// are assigned the same name.
+							v.Name = name;
+						}
+						else
+						{
+							v.Name = context.AssignName(v);
+							context.AssignNameToLocalSignatureIndex(v.Function, v.Index.Value, v.Name);
+						}
+						break;
+					default:
 						v.Name = context.AssignName(v);
-						context.AssignNameToLocalSignatureIndex(v.Function, v.Index.Value, v.Name);
-					}
-					break;
-				default:
-					v.Name = context.AssignName(v);
-					break;
+						break;
+				}
 			}
 
-			return default;
+			return VisitChildren(inst, context);
 		}
 
 		protected internal override Unit VisitILFunction(ILFunction function, VariableScope context)
@@ -515,7 +534,18 @@ namespace ICSharpCode.Decompiler.IL.Transforms
 				context.Add((MethodDefinitionHandle)function.ReducedMethod.MetadataToken, newName);
 			}
 
-			return base.VisitILFunction(function, new VariableScope(function, this.context, context));
+			var nestedContext = new VariableScope(function, this.context, context);
+			base.VisitILFunction(function, nestedContext);
+
+			if (function.Kind != ILFunctionKind.TopLevelFunction)
+			{
+				foreach (var p in function.Variables.Where(v => v.Kind == VariableKind.Parameter && v.Index >= 0))
+				{
+					p.Name = nestedContext.AssignNameIfUnassigned(p);
+				}
+			}
+
+			return default;
 		}
 
 		protected internal override Unit VisitCall(Call inst, VariableScope context)

From 8a67f48e4e8d7320ea5b71e5c52f2b154a57ae14 Mon Sep 17 00:00:00 2001
From: Siegfried Pammer <siegfriedpammer@gmail.com>
Date: Sun, 2 Mar 2025 22:18:24 +0100
Subject: [PATCH 42/49] Fix #1956: Adapt previous fix for variable names that
 have a number as suffix.

---
 ICSharpCode.Decompiler/IL/Transforms/AssignVariableNames.cs | 3 ++-
 1 file changed, 2 insertions(+), 1 deletion(-)

diff --git a/ICSharpCode.Decompiler/IL/Transforms/AssignVariableNames.cs b/ICSharpCode.Decompiler/IL/Transforms/AssignVariableNames.cs
index 24f7079fc..e0029d00e 100644
--- a/ICSharpCode.Decompiler/IL/Transforms/AssignVariableNames.cs
+++ b/ICSharpCode.Decompiler/IL/Transforms/AssignVariableNames.cs
@@ -215,7 +215,8 @@ namespace ICSharpCode.Decompiler.IL.Transforms
 							if (variables.TryGetValue(i, out var v))
 								variableMapping[v] = p.Name;
 						}
-						if (!parentScope.IsReservedVariableName(p.Name, out _))
+						string nameWithoutNumber = SplitName(p.Name, out int newIndex);
+						if (!parentScope.IsReservedVariableName(nameWithoutNumber, out _))
 						{
 							AddExistingName(reservedVariableNames, p.Name);
 							if (variables.TryGetValue(i, out var v))

From 355a039b59f137abf1bb7ca1fcf2a068c3a5de08 Mon Sep 17 00:00:00 2001
From: Siegfried Pammer <siegfriedpammer@gmail.com>
Date: Sun, 2 Mar 2025 22:47:30 +0100
Subject: [PATCH 43/49] Sightly improve variable naming of known types such as
 EventArgs and Exceptions

---
 .../TestCases/ILPretty/GuessAccessors.cs      | 12 +++----
 .../TestCases/Pretty/ExceptionHandling.cs     | 12 +++----
 .../IL/Transforms/AssignVariableNames.cs      | 34 +++++++++----------
 3 files changed, 29 insertions(+), 29 deletions(-)

diff --git a/ICSharpCode.Decompiler.Tests/TestCases/ILPretty/GuessAccessors.cs b/ICSharpCode.Decompiler.Tests/TestCases/ILPretty/GuessAccessors.cs
index 423ad4119..69ebf0db9 100644
--- a/ICSharpCode.Decompiler.Tests/TestCases/ILPretty/GuessAccessors.cs
+++ b/ICSharpCode.Decompiler.Tests/TestCases/ILPretty/GuessAccessors.cs
@@ -50,9 +50,9 @@ namespace ClassLibrary1
 			//IL_00e1: Expected O, but got Unknown
 			//IL_00e1: Expected O, but got Unknown
 			UnknownGenericClass<UnknownEventArgs> val = new UnknownGenericClass<UnknownEventArgs>();
-			UnknownEventArgs val2 = (val.UnknownProperty = val.UnknownProperty);
+			UnknownEventArgs e = (val.UnknownProperty = val.UnknownProperty);
 			List<object> list = new List<object> {
-				val[((object)val2).GetHashCode()] ?? "",
+				val[((object)e).GetHashCode()] ?? "",
 				val.NotProperty,
 				val.get_NotPropertyWithGeneric<string>(42),
 				val[42],
@@ -61,10 +61,10 @@ namespace ClassLibrary1
 			};
 			val.OnEvent += Instance_OnEvent;
 			val.OnEvent -= Instance_OnEvent;
-			UnknownEventArgs val3 = val[(UnknownEventArgs)null];
-			val[new UnknownEventArgs()] = val3;
-			UnknownEventArgs val4 = val[new UnknownEventArgs(), new UnknownEventArgs()];
-			val[new UnknownEventArgs(), new UnknownEventArgs()] = val4;
+			UnknownEventArgs e2 = val[(UnknownEventArgs)null];
+			val[new UnknownEventArgs()] = e2;
+			UnknownEventArgs e3 = val[new UnknownEventArgs(), new UnknownEventArgs()];
+			val[new UnknownEventArgs(), new UnknownEventArgs()] = e3;
 		}
 
 		public void MethodUnknownStatic()
diff --git a/ICSharpCode.Decompiler.Tests/TestCases/Pretty/ExceptionHandling.cs b/ICSharpCode.Decompiler.Tests/TestCases/Pretty/ExceptionHandling.cs
index 82aba9ab6..2e47643f0 100644
--- a/ICSharpCode.Decompiler.Tests/TestCases/Pretty/ExceptionHandling.cs
+++ b/ICSharpCode.Decompiler.Tests/TestCases/Pretty/ExceptionHandling.cs
@@ -418,9 +418,9 @@ namespace ICSharpCode.Decompiler.Tests.TestCases.Pretty
 			{
 				Console.WriteLine(input);
 			}
-			catch (TException val)
+			catch (TException ex)
 			{
-				Console.WriteLine(val.Message);
+				Console.WriteLine(ex.Message);
 				throw;
 			}
 		}
@@ -452,9 +452,9 @@ namespace ICSharpCode.Decompiler.Tests.TestCases.Pretty
 			{
 				Console.WriteLine(input);
 			}
-			catch (TException val) when (val.Message.Contains("Test"))
+			catch (TException ex) when (ex.Message.Contains("Test"))
 			{
-				Console.WriteLine(val.Message);
+				Console.WriteLine(ex.Message);
 				throw;
 			}
 		}
@@ -465,9 +465,9 @@ namespace ICSharpCode.Decompiler.Tests.TestCases.Pretty
 			{
 				Console.WriteLine(input);
 			}
-			catch (TException val) when (val.Message.Contains("Test"))
+			catch (TException ex) when (ex.Message.Contains("Test"))
 			{
-				Console.WriteLine("{0} {1}", val, val.ToString());
+				Console.WriteLine("{0} {1}", ex, ex.ToString());
 			}
 		}
 
diff --git a/ICSharpCode.Decompiler/IL/Transforms/AssignVariableNames.cs b/ICSharpCode.Decompiler/IL/Transforms/AssignVariableNames.cs
index e0029d00e..dbe8c5dd4 100644
--- a/ICSharpCode.Decompiler/IL/Transforms/AssignVariableNames.cs
+++ b/ICSharpCode.Decompiler/IL/Transforms/AssignVariableNames.cs
@@ -729,22 +729,7 @@ namespace ICSharpCode.Decompiler.IL.Transforms
 				type = NullableType.GetUnderlyingType(((TypeWithElementType)type).ElementType);
 			}
 
-			string name = type.Kind switch {
-				TypeKind.Array => "array",
-				TypeKind.Pointer => "ptr",
-				TypeKind.TypeParameter => "val",
-				TypeKind.Unknown => "val",
-				TypeKind.Dynamic => "val",
-				TypeKind.ByReference => "reference",
-				TypeKind.Tuple => "tuple",
-				TypeKind.NInt => "num",
-				TypeKind.NUInt => "num",
-				_ => null
-			};
-			if (name != null)
-			{
-				return name;
-			}
+			string name;
 			if (type.IsAnonymousType())
 			{
 				name = "anon";
@@ -753,13 +738,28 @@ namespace ICSharpCode.Decompiler.IL.Transforms
 			{
 				name = "ex";
 			}
+			else if (type.Name.EndsWith("EventArgs", StringComparison.Ordinal))
+			{
+				name = "e";
+			}
 			else if (type.IsCSharpNativeIntegerType())
 			{
 				name = "num";
 			}
 			else if (!typeNameToVariableNameDict.TryGetValue(type.FullName, out name))
 			{
-				name = type.Name;
+				name = type.Kind switch {
+					TypeKind.Array => "array",
+					TypeKind.Pointer => "ptr",
+					TypeKind.TypeParameter => "val",
+					TypeKind.Unknown => "val",
+					TypeKind.Dynamic => "val",
+					TypeKind.ByReference => "reference",
+					TypeKind.Tuple => "tuple",
+					TypeKind.NInt => "num",
+					TypeKind.NUInt => "num",
+					_ => type.Name
+				};
 				// remove the 'I' for interfaces
 				if (name.Length >= 3 && name[0] == 'I' && char.IsUpper(name[1]) && char.IsLower(name[2]))
 					name = name.Substring(1);

From 356d7a1b436f70dfbfa5080b75492f8623d2ceed Mon Sep 17 00:00:00 2001
From: Siegfried Pammer <siegfriedpammer@gmail.com>
Date: Fri, 21 Mar 2025 10:05:08 +0100
Subject: [PATCH 44/49] Fix #3408: Wrong exported assembly type

---
 .../CSharp/ProjectDecompiler/ProjectFileWriterDefault.cs   | 7 ++++---
 1 file changed, 4 insertions(+), 3 deletions(-)

diff --git a/ICSharpCode.Decompiler/CSharp/ProjectDecompiler/ProjectFileWriterDefault.cs b/ICSharpCode.Decompiler/CSharp/ProjectDecompiler/ProjectFileWriterDefault.cs
index a536d72c7..dba1f0f4c 100644
--- a/ICSharpCode.Decompiler/CSharp/ProjectDecompiler/ProjectFileWriterDefault.cs
+++ b/ICSharpCode.Decompiler/CSharp/ProjectDecompiler/ProjectFileWriterDefault.cs
@@ -81,13 +81,14 @@ namespace ICSharpCode.Decompiler.CSharp.ProjectDecompiler
 				w.WriteEndElement(); // </Platform>
 
 				string outputType;
+				PEHeaders headers = (module as PEFile)?.Reader.PEHeaders;
 
-				switch ((module as PEFile)?.Reader.PEHeaders.PEHeader.Subsystem)
+				switch (headers?.PEHeader.Subsystem)
 				{
-					case Subsystem.WindowsGui:
+					case Subsystem.WindowsGui when !headers.IsDll:
 						outputType = "WinExe";
 						break;
-					case Subsystem.WindowsCui:
+					case Subsystem.WindowsCui when !headers.IsDll:
 						outputType = "Exe";
 						break;
 					default:

From 29861d7903cf9aceb9b074991d2d7f5e5c2f2ecc Mon Sep 17 00:00:00 2001
From: Siegfried Pammer <siegfriedpammer@gmail.com>
Date: Fri, 21 Mar 2025 11:00:11 +0100
Subject: [PATCH 45/49] Fix #1647: Add a cast to enum constants where the enum
 type is not known.

---
 ICSharpCode.Decompiler/CSharp/Syntax/TypeSystemAstBuilder.cs | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/ICSharpCode.Decompiler/CSharp/Syntax/TypeSystemAstBuilder.cs b/ICSharpCode.Decompiler/CSharp/Syntax/TypeSystemAstBuilder.cs
index 63b697d75..2e21d93c6 100644
--- a/ICSharpCode.Decompiler/CSharp/Syntax/TypeSystemAstBuilder.cs
+++ b/ICSharpCode.Decompiler/CSharp/Syntax/TypeSystemAstBuilder.cs
@@ -1134,7 +1134,7 @@ namespace ICSharpCode.Decompiler.CSharp.Syntax
 					expr = new PrimitiveExpression(constantValue, format);
 					if (AddResolveResultAnnotations && literalType != null)
 						expr.AddAnnotation(new ConstantResolveResult(literalType, constantValue));
-					if (integerTypeMismatch && !type.Equals(expectedType))
+					if (integerTypeMismatch && !type.Equals(expectedType) || underlyingType.Kind == TypeKind.Unknown)
 					{
 						expr = new CastExpression(ConvertType(type), expr);
 					}

From 51522c448784d31b4ce0058261436f0de573ef14 Mon Sep 17 00:00:00 2001
From: Siegfried Pammer <siegfriedpammer@gmail.com>
Date: Sat, 22 Mar 2025 10:14:11 +0100
Subject: [PATCH 46/49] Fix #3190: NRE in YieldReturnDecompiler

---
 ICSharpCode.Decompiler/IL/ControlFlow/YieldReturnDecompiler.cs | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/ICSharpCode.Decompiler/IL/ControlFlow/YieldReturnDecompiler.cs b/ICSharpCode.Decompiler/IL/ControlFlow/YieldReturnDecompiler.cs
index c5ef712bc..5a8565a8d 100644
--- a/ICSharpCode.Decompiler/IL/ControlFlow/YieldReturnDecompiler.cs
+++ b/ICSharpCode.Decompiler/IL/ControlFlow/YieldReturnDecompiler.cs
@@ -1014,7 +1014,7 @@ namespace ICSharpCode.Decompiler.IL.ControlFlow
 				}
 				// We can't use MatchStLoc like above since the doFinallyBodies variable is split by SplitVariables.
 				// This occurs for the Legacy VBC compiler.
-				if (oldBlock.Instructions[pos].MatchStLoc(out var var, out value) && var.Kind == VariableKind.Local && var.Index == doFinallyBodies.Index)
+				if (oldBlock.Instructions[pos].MatchStLoc(out var var, out value) && var.Kind == VariableKind.Local && var.Index == doFinallyBodies?.Index)
 				{
 					if (!value.MatchLdcI4(0))
 					{

From 128f83d74cdef3cf6065977b2df7414ba950e22d Mon Sep 17 00:00:00 2001
From: Siegfried Pammer <siegfriedpammer@gmail.com>
Date: Sat, 22 Mar 2025 12:19:47 +0100
Subject: [PATCH 47/49] Fix: Pass current language version to all parts of the
 UI

---
 ILSpy/ExtensionMethods.cs                     |  8 ++++--
 ILSpy/Languages/CSharpLanguage.cs             | 25 ++++++++++++++++---
 ILSpy/Languages/LanguageService.cs            |  2 ++
 .../Metadata/CorTables/EventTableTreeNode.cs  |  2 +-
 .../Metadata/CorTables/FieldTableTreeNode.cs  |  2 +-
 .../Metadata/CorTables/MethodTableTreeNode.cs |  2 +-
 .../CorTables/PropertyTableTreeNode.cs        |  2 +-
 .../CorTables/TypeDefTableTreeNode.cs         |  2 +-
 ILSpy/Search/SearchPane.xaml.cs               | 10 ++++++--
 ILSpy/TreeNodes/AssemblyReferenceTreeNode.cs  |  2 +-
 ILSpy/TreeNodes/EventTreeNode.cs              |  2 +-
 ILSpy/TreeNodes/FieldTreeNode.cs              |  2 +-
 ILSpy/TreeNodes/MethodTreeNode.cs             |  2 +-
 ILSpy/TreeNodes/PropertyTreeNode.cs           |  2 +-
 ILSpy/TreeNodes/ReferenceFolderTreeNode.cs    |  2 +-
 ILSpy/TreeNodes/TypeTreeNode.cs               |  2 +-
 16 files changed, 50 insertions(+), 19 deletions(-)

diff --git a/ILSpy/ExtensionMethods.cs b/ILSpy/ExtensionMethods.cs
index bd460826c..60bb77d40 100644
--- a/ILSpy/ExtensionMethods.cs
+++ b/ILSpy/ExtensionMethods.cs
@@ -76,11 +76,15 @@ namespace ICSharpCode.ILSpy
 			return result;
 		}
 
-		public static ICompilation? GetTypeSystemWithCurrentOptionsOrNull(this MetadataFile file, SettingsService settingsService)
+		public static ICompilation? GetTypeSystemWithCurrentOptionsOrNull(this MetadataFile file, SettingsService settingsService, LanguageVersion languageVersion)
 		{
+			var decompilerSettings = settingsService.DecompilerSettings.Clone();
+			if (!Enum.TryParse(languageVersion.Version, out Decompiler.CSharp.LanguageVersion csharpLanguageVersion))
+				csharpLanguageVersion = Decompiler.CSharp.LanguageVersion.Latest;
+			decompilerSettings.SetLanguageVersion(csharpLanguageVersion);
 			return file
 				.GetLoadedAssembly()
-				.GetTypeSystemOrNull(DecompilerTypeSystem.GetOptions(settingsService.DecompilerSettings));
+				.GetTypeSystemOrNull(DecompilerTypeSystem.GetOptions(decompilerSettings));
 		}
 
 		#region DPI independence
diff --git a/ILSpy/Languages/CSharpLanguage.cs b/ILSpy/Languages/CSharpLanguage.cs
index b1686836c..73336640e 100644
--- a/ILSpy/Languages/CSharpLanguage.cs
+++ b/ILSpy/Languages/CSharpLanguage.cs
@@ -578,10 +578,18 @@ namespace ICSharpCode.ILSpy
 			CSharpAmbience ambience = new CSharpAmbience();
 			// Do not forget to update CSharpAmbienceTests.ILSpyMainTreeViewTypeFlags, if this ever changes.
 			ambience.ConversionFlags = ConversionFlags.ShowTypeParameterList | ConversionFlags.PlaceReturnTypeAfterParameterList;
-			if (SettingsService.DecompilerSettings.LiftNullables)
+			var decompilerSettings = SettingsService.DecompilerSettings.Clone();
+			if (!Enum.TryParse(AssemblyTreeModel.CurrentLanguageVersion?.Version, out Decompiler.CSharp.LanguageVersion languageVersion))
+				languageVersion = Decompiler.CSharp.LanguageVersion.Latest;
+			decompilerSettings.SetLanguageVersion(languageVersion);
+			if (decompilerSettings.LiftNullables)
 			{
 				ambience.ConversionFlags |= ConversionFlags.UseNullableSpecifierForValueTypes;
 			}
+			if (decompilerSettings.IntroducePrivateProtectedAccessibility)
+			{
+				ambience.ConversionFlags |= ConversionFlags.UsePrivateProtectedAccessibility;
+			}
 			return ambience;
 		}
 
@@ -781,7 +789,11 @@ namespace ICSharpCode.ILSpy
 		public override bool ShowMember(IEntity member)
 		{
 			MetadataFile assembly = member.ParentModule.MetadataFile;
-			return showAllMembers || !CSharpDecompiler.MemberIsHidden(assembly, member.MetadataToken, SettingsService.DecompilerSettings);
+			var decompilerSettings = SettingsService.DecompilerSettings.Clone();
+			if (!Enum.TryParse(AssemblyTreeModel.CurrentLanguageVersion?.Version, out Decompiler.CSharp.LanguageVersion languageVersion))
+				languageVersion = Decompiler.CSharp.LanguageVersion.Latest;
+			decompilerSettings.SetLanguageVersion(languageVersion);
+			return showAllMembers || !CSharpDecompiler.MemberIsHidden(assembly, member.MetadataToken, decompilerSettings);
 		}
 
 		public override RichText GetRichTextTooltip(IEntity entity)
@@ -790,7 +802,10 @@ namespace ICSharpCode.ILSpy
 			var output = new StringWriter();
 			var decoratedWriter = new TextWriterTokenWriter(output);
 			var writer = new CSharpHighlightingTokenWriter(TokenWriter.InsertRequiredSpaces(decoratedWriter), locatable: decoratedWriter);
-			var settings = SettingsService.DecompilerSettings;
+			var settings = SettingsService.DecompilerSettings.Clone();
+			if (!Enum.TryParse(AssemblyTreeModel.CurrentLanguageVersion?.Version, out Decompiler.CSharp.LanguageVersion languageVersion))
+				languageVersion = Decompiler.CSharp.LanguageVersion.Latest;
+			settings.SetLanguageVersion(languageVersion);
 			if (!settings.LiftNullables)
 			{
 				flags &= ~ConversionFlags.UseNullableSpecifierForValueTypes;
@@ -815,6 +830,10 @@ namespace ICSharpCode.ILSpy
 			{
 				flags |= ConversionFlags.SupportInitAccessors;
 			}
+			if (settings.IntroducePrivateProtectedAccessibility)
+			{
+				flags |= ConversionFlags.UsePrivateProtectedAccessibility;
+			}
 			if (entity is IMethod m && m.IsLocalFunction)
 			{
 				writer.WriteIdentifier(Identifier.Create("(local)"));
diff --git a/ILSpy/Languages/LanguageService.cs b/ILSpy/Languages/LanguageService.cs
index 6c116b1ad..7cd6e47e1 100644
--- a/ILSpy/Languages/LanguageService.cs
+++ b/ILSpy/Languages/LanguageService.cs
@@ -35,10 +35,12 @@ namespace ICSharpCode.ILSpy
 	[Shared]
 	public class LanguageService : ObservableObjectBase
 	{
+		private readonly SettingsService settingsService;
 		private readonly LanguageSettings languageSettings;
 
 		public LanguageService(IEnumerable<Language> languages, SettingsService settingsService, DockWorkspace dockWorkspace)
 		{
+			this.settingsService = settingsService;
 			languageSettings = settingsService.SessionSettings.LanguageSettings;
 
 			var sortedLanguages = languages.ToList();
diff --git a/ILSpy/Metadata/CorTables/EventTableTreeNode.cs b/ILSpy/Metadata/CorTables/EventTableTreeNode.cs
index 821a25476..d63ab0ef3 100644
--- a/ILSpy/Metadata/CorTables/EventTableTreeNode.cs
+++ b/ILSpy/Metadata/CorTables/EventTableTreeNode.cs
@@ -94,7 +94,7 @@ namespace ICSharpCode.ILSpy.Metadata
 
 			IEntity IMemberTreeNode.Member {
 				get {
-					return ((MetadataModule)metadataFile.GetTypeSystemWithCurrentOptionsOrNull(SettingsService)?.MainModule)?.GetDefinition(handle);
+					return ((MetadataModule)metadataFile.GetTypeSystemWithCurrentOptionsOrNull(SettingsService, AssemblyTreeModel.CurrentLanguageVersion)?.MainModule)?.GetDefinition(handle);
 				}
 			}
 
diff --git a/ILSpy/Metadata/CorTables/FieldTableTreeNode.cs b/ILSpy/Metadata/CorTables/FieldTableTreeNode.cs
index eb48eaec2..345600d7c 100644
--- a/ILSpy/Metadata/CorTables/FieldTableTreeNode.cs
+++ b/ILSpy/Metadata/CorTables/FieldTableTreeNode.cs
@@ -95,7 +95,7 @@ namespace ICSharpCode.ILSpy.Metadata
 
 			public string NameTooltip => $"{MetadataTokens.GetHeapOffset(fieldDef.Name):X} \"{Name}\"";
 
-			IEntity IMemberTreeNode.Member => ((MetadataModule)metadataFile.GetTypeSystemWithCurrentOptionsOrNull(SettingsService)?.MainModule)?.GetDefinition(handle);
+			IEntity IMemberTreeNode.Member => ((MetadataModule)metadataFile.GetTypeSystemWithCurrentOptionsOrNull(SettingsService, AssemblyTreeModel.CurrentLanguageVersion)?.MainModule)?.GetDefinition(handle);
 
 			[ColumnInfo("X8", Kind = ColumnKind.HeapOffset)]
 			public int Signature => MetadataTokens.GetHeapOffset(fieldDef.Signature);
diff --git a/ILSpy/Metadata/CorTables/MethodTableTreeNode.cs b/ILSpy/Metadata/CorTables/MethodTableTreeNode.cs
index 775661855..4f2d7b712 100644
--- a/ILSpy/Metadata/CorTables/MethodTableTreeNode.cs
+++ b/ILSpy/Metadata/CorTables/MethodTableTreeNode.cs
@@ -131,7 +131,7 @@ namespace ICSharpCode.ILSpy.Metadata
 				}
 			}
 
-			IEntity IMemberTreeNode.Member => ((MetadataModule)metadataFile.GetTypeSystemWithCurrentOptionsOrNull(SettingsService)?.MainModule)?.GetDefinition(handle);
+			IEntity IMemberTreeNode.Member => ((MetadataModule)metadataFile.GetTypeSystemWithCurrentOptionsOrNull(SettingsService, AssemblyTreeModel.CurrentLanguageVersion)?.MainModule)?.GetDefinition(handle);
 
 			public MethodDefEntry(MetadataFile metadataFile, MethodDefinitionHandle handle)
 			{
diff --git a/ILSpy/Metadata/CorTables/PropertyTableTreeNode.cs b/ILSpy/Metadata/CorTables/PropertyTableTreeNode.cs
index 255892ea1..87d187d28 100644
--- a/ILSpy/Metadata/CorTables/PropertyTableTreeNode.cs
+++ b/ILSpy/Metadata/CorTables/PropertyTableTreeNode.cs
@@ -92,7 +92,7 @@ namespace ICSharpCode.ILSpy.Metadata
 
 			public string NameTooltip => $"{MetadataTokens.GetHeapOffset(propertyDef.Name):X} \"{Name}\"";
 
-			IEntity IMemberTreeNode.Member => ((MetadataModule)metadataFile.GetTypeSystemWithCurrentOptionsOrNull(SettingsService)?.MainModule)?.GetDefinition(handle);
+			IEntity IMemberTreeNode.Member => ((MetadataModule)metadataFile.GetTypeSystemWithCurrentOptionsOrNull(SettingsService, AssemblyTreeModel.CurrentLanguageVersion)?.MainModule)?.GetDefinition(handle);
 
 			[ColumnInfo("X8", Kind = ColumnKind.HeapOffset)]
 			public int Signature => MetadataTokens.GetHeapOffset(propertyDef.Signature);
diff --git a/ILSpy/Metadata/CorTables/TypeDefTableTreeNode.cs b/ILSpy/Metadata/CorTables/TypeDefTableTreeNode.cs
index e7aa7bbd4..d5f51939d 100644
--- a/ILSpy/Metadata/CorTables/TypeDefTableTreeNode.cs
+++ b/ILSpy/Metadata/CorTables/TypeDefTableTreeNode.cs
@@ -173,7 +173,7 @@ namespace ICSharpCode.ILSpy.Metadata
 				}
 			}
 
-			IEntity IMemberTreeNode.Member => ((MetadataModule)metadataFile.GetTypeSystemWithCurrentOptionsOrNull(SettingsService)?.MainModule)?.GetDefinition(handle);
+			IEntity IMemberTreeNode.Member => ((MetadataModule)metadataFile.GetTypeSystemWithCurrentOptionsOrNull(SettingsService, AssemblyTreeModel.CurrentLanguageVersion)?.MainModule)?.GetDefinition(handle);
 
 			public TypeDefEntry(MetadataFile metadataFile, TypeDefinitionHandle handle)
 			{
diff --git a/ILSpy/Search/SearchPane.xaml.cs b/ILSpy/Search/SearchPane.xaml.cs
index 83ec4cc1e..190d65b81 100644
--- a/ILSpy/Search/SearchPane.xaml.cs
+++ b/ILSpy/Search/SearchPane.xaml.cs
@@ -266,6 +266,7 @@ namespace ICSharpCode.ILSpy.Search
 					searchTerm,
 					(SearchMode)searchModeComboBox.SelectedIndex,
 					assemblyTreeModel.CurrentLanguage,
+					assemblyTreeModel.CurrentLanguageVersion,
 					treeNodeFactory,
 					settingsService);
 				currentSearch = startedSearch;
@@ -295,6 +296,7 @@ namespace ICSharpCode.ILSpy.Search
 			readonly SearchRequest searchRequest;
 			readonly SearchMode searchMode;
 			readonly Language language;
+			readonly LanguageVersion languageVersion;
 			readonly ApiVisibility apiVisibility;
 			readonly ITreeNodeFactory treeNodeFactory;
 			readonly SettingsService settingsService;
@@ -302,7 +304,7 @@ namespace ICSharpCode.ILSpy.Search
 			public IProducerConsumerCollection<SearchResult> ResultQueue { get; } = new ConcurrentQueue<SearchResult>();
 
 			public RunningSearch(IList<LoadedAssembly> assemblies, string searchTerm, SearchMode searchMode,
-				Language language, ITreeNodeFactory treeNodeFactory, SettingsService settingsService)
+				Language language, LanguageVersion languageVersion, ITreeNodeFactory treeNodeFactory, SettingsService settingsService)
 			{
 				this.assemblies = assemblies;
 				this.language = language;
@@ -471,7 +473,11 @@ namespace ICSharpCode.ILSpy.Search
 				request.RegEx = regex;
 				request.SearchResultFactory = new SearchResultFactory(language);
 				request.TreeNodeFactory = this.treeNodeFactory;
-				request.DecompilerSettings = settingsService.DecompilerSettings;
+				var decompilerSettings = settingsService.DecompilerSettings.Clone();
+				if (!Enum.TryParse(this.languageVersion?.Version, out Decompiler.CSharp.LanguageVersion languageVersion))
+					languageVersion = Decompiler.CSharp.LanguageVersion.Latest;
+				decompilerSettings.SetLanguageVersion(languageVersion);
+				request.DecompilerSettings = settingsService.DecompilerSettings.Clone();
 
 				return request;
 			}
diff --git a/ILSpy/TreeNodes/AssemblyReferenceTreeNode.cs b/ILSpy/TreeNodes/AssemblyReferenceTreeNode.cs
index c62af32d8..b779350d6 100644
--- a/ILSpy/TreeNodes/AssemblyReferenceTreeNode.cs
+++ b/ILSpy/TreeNodes/AssemblyReferenceTreeNode.cs
@@ -89,7 +89,7 @@ namespace ICSharpCode.ILSpy.TreeNodes
 			var referencedModule = resolver.Resolve(r);
 			if (referencedModule != null)
 			{
-				var module = (MetadataModule)referencedModule.GetTypeSystemWithCurrentOptionsOrNull(SettingsService)?.MainModule;
+				var module = (MetadataModule)referencedModule.GetTypeSystemWithCurrentOptionsOrNull(SettingsService, AssemblyTreeModel.CurrentLanguageVersion)?.MainModule;
 				foreach (var childRef in referencedModule.AssemblyReferences)
 					this.Children.Add(new AssemblyReferenceTreeNode(module, childRef, parentAssembly));
 			}
diff --git a/ILSpy/TreeNodes/EventTreeNode.cs b/ILSpy/TreeNodes/EventTreeNode.cs
index ad75b1b5b..1af0a9de3 100644
--- a/ILSpy/TreeNodes/EventTreeNode.cs
+++ b/ILSpy/TreeNodes/EventTreeNode.cs
@@ -52,7 +52,7 @@ namespace ICSharpCode.ILSpy.TreeNodes
 		private IEvent GetEventDefinition()
 		{
 			return ((MetadataModule)EventDefinition.ParentModule?.MetadataFile
-				?.GetTypeSystemWithCurrentOptionsOrNull(SettingsService)
+				?.GetTypeSystemWithCurrentOptionsOrNull(SettingsService, AssemblyTreeModel.CurrentLanguageVersion)
 				?.MainModule)?.GetDefinition((EventDefinitionHandle)EventDefinition.MetadataToken) ?? EventDefinition;
 		}
 
diff --git a/ILSpy/TreeNodes/FieldTreeNode.cs b/ILSpy/TreeNodes/FieldTreeNode.cs
index 26319a03d..4a086ceb7 100644
--- a/ILSpy/TreeNodes/FieldTreeNode.cs
+++ b/ILSpy/TreeNodes/FieldTreeNode.cs
@@ -44,7 +44,7 @@ namespace ICSharpCode.ILSpy.TreeNodes
 		private IField GetFieldDefinition()
 		{
 			return ((MetadataModule)FieldDefinition.ParentModule?.MetadataFile
-				?.GetTypeSystemWithCurrentOptionsOrNull(SettingsService)
+				?.GetTypeSystemWithCurrentOptionsOrNull(SettingsService, AssemblyTreeModel.CurrentLanguageVersion)
 				?.MainModule)?.GetDefinition((FieldDefinitionHandle)FieldDefinition.MetadataToken) ?? FieldDefinition;
 		}
 
diff --git a/ILSpy/TreeNodes/MethodTreeNode.cs b/ILSpy/TreeNodes/MethodTreeNode.cs
index f3d2a6daa..1658ac714 100644
--- a/ILSpy/TreeNodes/MethodTreeNode.cs
+++ b/ILSpy/TreeNodes/MethodTreeNode.cs
@@ -44,7 +44,7 @@ namespace ICSharpCode.ILSpy.TreeNodes
 		private IMethod GetMethodDefinition()
 		{
 			return ((MetadataModule)MethodDefinition.ParentModule?.MetadataFile
-				?.GetTypeSystemWithCurrentOptionsOrNull(SettingsService)
+				?.GetTypeSystemWithCurrentOptionsOrNull(SettingsService, AssemblyTreeModel.CurrentLanguageVersion)
 				?.MainModule)?.GetDefinition((MethodDefinitionHandle)MethodDefinition.MetadataToken) ?? MethodDefinition;
 		}
 
diff --git a/ILSpy/TreeNodes/PropertyTreeNode.cs b/ILSpy/TreeNodes/PropertyTreeNode.cs
index df1a9f473..fc0a0ccbc 100644
--- a/ILSpy/TreeNodes/PropertyTreeNode.cs
+++ b/ILSpy/TreeNodes/PropertyTreeNode.cs
@@ -54,7 +54,7 @@ namespace ICSharpCode.ILSpy.TreeNodes
 		private IProperty GetPropertyDefinition()
 		{
 			return ((MetadataModule)PropertyDefinition.ParentModule?.MetadataFile
-				?.GetTypeSystemWithCurrentOptionsOrNull(SettingsService)
+				?.GetTypeSystemWithCurrentOptionsOrNull(SettingsService, AssemblyTreeModel.CurrentLanguageVersion)
 				?.MainModule)?.GetDefinition((PropertyDefinitionHandle)PropertyDefinition.MetadataToken) ?? PropertyDefinition;
 		}
 
diff --git a/ILSpy/TreeNodes/ReferenceFolderTreeNode.cs b/ILSpy/TreeNodes/ReferenceFolderTreeNode.cs
index c8c4dd8d0..61fc007b7 100644
--- a/ILSpy/TreeNodes/ReferenceFolderTreeNode.cs
+++ b/ILSpy/TreeNodes/ReferenceFolderTreeNode.cs
@@ -49,7 +49,7 @@ namespace ICSharpCode.ILSpy.TreeNodes
 		protected override void LoadChildren()
 		{
 			var metadata = module.Metadata;
-			var metadataModule = (MetadataModule)module.GetTypeSystemWithCurrentOptionsOrNull(SettingsService)?.MainModule;
+			var metadataModule = (MetadataModule)module.GetTypeSystemWithCurrentOptionsOrNull(SettingsService, AssemblyTreeModel.CurrentLanguageVersion)?.MainModule;
 			foreach (var r in module.AssemblyReferences.OrderBy(r => r.Name))
 				this.Children.Add(new AssemblyReferenceTreeNode(metadataModule, r, parentAssembly));
 			foreach (var r in metadata.GetModuleReferences().OrderBy(r => metadata.GetString(metadata.GetModuleReference(r).Name)))
diff --git a/ILSpy/TreeNodes/TypeTreeNode.cs b/ILSpy/TreeNodes/TypeTreeNode.cs
index a79b5c654..edb4d3567 100644
--- a/ILSpy/TreeNodes/TypeTreeNode.cs
+++ b/ILSpy/TreeNodes/TypeTreeNode.cs
@@ -49,7 +49,7 @@ namespace ICSharpCode.ILSpy.TreeNodes
 		{
 			return ((MetadataModule)ParentAssemblyNode.LoadedAssembly
 				.GetMetadataFileOrNull()
-				?.GetTypeSystemWithCurrentOptionsOrNull(SettingsService)
+				?.GetTypeSystemWithCurrentOptionsOrNull(SettingsService, AssemblyTreeModel.CurrentLanguageVersion)
 				?.MainModule)?.GetDefinition((SRM.TypeDefinitionHandle)TypeDefinition.MetadataToken);
 		}
 

From 96caa4ecb793c2e3215f44b7c05f1bd4d9ccf08e Mon Sep 17 00:00:00 2001
From: Siegfried Pammer <siegfriedpammer@gmail.com>
Date: Sat, 22 Mar 2025 12:27:22 +0100
Subject: [PATCH 48/49] Fix: #3407 Add "private protected" feature for 7.2
 decompiler options

---
 .../CSharp/CSharpDecompiler.cs                |  1 +
 .../CSharp/OutputVisitor/CSharpAmbience.cs    |  1 +
 .../CSharp/Syntax/TypeSystemAstBuilder.cs     | 21 ++++++++++++-------
 ICSharpCode.Decompiler/DecompilerSettings.cs  | 21 ++++++++++++++++++-
 ICSharpCode.Decompiler/Output/IAmbience.cs    |  5 +++++
 ILSpy/Properties/Resources.Designer.cs        | 19 +++++++++++++++++
 ILSpy/Properties/Resources.resx               |  5 ++++-
 ILSpy/Properties/Resources.zh-Hans.resx       |  3 +++
 8 files changed, 67 insertions(+), 9 deletions(-)

diff --git a/ICSharpCode.Decompiler/CSharp/CSharpDecompiler.cs b/ICSharpCode.Decompiler/CSharp/CSharpDecompiler.cs
index b9559dba1..6c881715f 100644
--- a/ICSharpCode.Decompiler/CSharp/CSharpDecompiler.cs
+++ b/ICSharpCode.Decompiler/CSharp/CSharpDecompiler.cs
@@ -528,6 +528,7 @@ namespace ICSharpCode.Decompiler.CSharp
 		{
 			var typeSystemAstBuilder = new TypeSystemAstBuilder();
 			typeSystemAstBuilder.ShowAttributes = true;
+			typeSystemAstBuilder.UsePrivateProtectedAccessibility = settings.IntroducePrivateProtectedAccessibility;
 			typeSystemAstBuilder.SortAttributes = settings.SortCustomAttributes;
 			typeSystemAstBuilder.AlwaysUseShortTypeNames = true;
 			typeSystemAstBuilder.AddResolveResultAnnotations = true;
diff --git a/ICSharpCode.Decompiler/CSharp/OutputVisitor/CSharpAmbience.cs b/ICSharpCode.Decompiler/CSharp/OutputVisitor/CSharpAmbience.cs
index aa290cbd8..35036232e 100644
--- a/ICSharpCode.Decompiler/CSharp/OutputVisitor/CSharpAmbience.cs
+++ b/ICSharpCode.Decompiler/CSharp/OutputVisitor/CSharpAmbience.cs
@@ -236,6 +236,7 @@ namespace ICSharpCode.Decompiler.CSharp.OutputVisitor
 			astBuilder.ShowTypeParametersForUnboundTypes = true;
 			astBuilder.ShowModifiers = (ConversionFlags & ConversionFlags.ShowModifiers) == ConversionFlags.ShowModifiers;
 			astBuilder.ShowAccessibility = (ConversionFlags & ConversionFlags.ShowAccessibility) == ConversionFlags.ShowAccessibility;
+			astBuilder.UsePrivateProtectedAccessibility = (ConversionFlags & ConversionFlags.UsePrivateProtectedAccessibility) == ConversionFlags.UsePrivateProtectedAccessibility;
 			astBuilder.AlwaysUseShortTypeNames = (ConversionFlags & ConversionFlags.UseFullyQualifiedTypeNames) != ConversionFlags.UseFullyQualifiedTypeNames;
 			astBuilder.ShowParameterNames = (ConversionFlags & ConversionFlags.ShowParameterNames) == ConversionFlags.ShowParameterNames;
 			astBuilder.UseNullableSpecifierForValueTypes = (ConversionFlags & ConversionFlags.UseNullableSpecifierForValueTypes) != 0;
diff --git a/ICSharpCode.Decompiler/CSharp/Syntax/TypeSystemAstBuilder.cs b/ICSharpCode.Decompiler/CSharp/Syntax/TypeSystemAstBuilder.cs
index 2e21d93c6..3583c6a44 100644
--- a/ICSharpCode.Decompiler/CSharp/Syntax/TypeSystemAstBuilder.cs
+++ b/ICSharpCode.Decompiler/CSharp/Syntax/TypeSystemAstBuilder.cs
@@ -70,6 +70,7 @@ namespace ICSharpCode.Decompiler.CSharp.Syntax
 			this.UseKeywordsForBuiltinTypes = true;
 			this.UseNullableSpecifierForValueTypes = true;
 			this.ShowAccessibility = true;
+			this.UsePrivateProtectedAccessibility = true;
 			this.ShowModifiers = true;
 			this.ShowBaseTypes = true;
 			this.ShowTypeParameters = true;
@@ -93,13 +94,19 @@ namespace ICSharpCode.Decompiler.CSharp.Syntax
 		public bool AddResolveResultAnnotations { get; set; }
 
 		/// <summary>
-		/// Controls the accessibility modifiers are shown.
+		/// Controls whether accessibility modifiers are shown.
 		/// The default value is <see langword="true" />.
 		/// </summary>
 		public bool ShowAccessibility { get; set; }
 
 		/// <summary>
-		/// Controls the non-accessibility modifiers are shown.
+		/// Controls whether "private protected" accessibility modifiers are shown.
+		/// The default value is <see langword="true" />.
+		/// </summary>
+		public bool UsePrivateProtectedAccessibility { get; set; }
+
+		/// <summary>
+		/// Controls whether non-accessibility modifiers are shown.
 		/// The default value is <see langword="true" />.
 		/// </summary>
 		public bool ShowModifiers { get; set; }
@@ -1805,7 +1812,7 @@ namespace ICSharpCode.Decompiler.CSharp.Syntax
 			Modifiers modifiers = Modifiers.None;
 			if (this.ShowAccessibility)
 			{
-				modifiers |= ModifierFromAccessibility(typeDefinition.Accessibility);
+				modifiers |= ModifierFromAccessibility(typeDefinition.Accessibility, UsePrivateProtectedAccessibility);
 			}
 			if (this.ShowModifiers)
 			{
@@ -2073,7 +2080,7 @@ namespace ICSharpCode.Decompiler.CSharp.Syntax
 				}
 			}
 			if (this.ShowAccessibility && accessor.Accessibility != ownerAccessibility)
-				decl.Modifiers = ModifierFromAccessibility(accessor.Accessibility);
+				decl.Modifiers = ModifierFromAccessibility(accessor.Accessibility, UsePrivateProtectedAccessibility);
 			if (this.ShowModifiers && accessor.HasReadonlyModifier())
 				decl.Modifiers |= Modifiers.Readonly;
 			TokenRole keywordRole = kind switch {
@@ -2342,7 +2349,7 @@ namespace ICSharpCode.Decompiler.CSharp.Syntax
 		#endregion
 
 		#region Convert Modifiers
-		public static Modifiers ModifierFromAccessibility(Accessibility accessibility)
+		public static Modifiers ModifierFromAccessibility(Accessibility accessibility, bool usePrivateProtected)
 		{
 			switch (accessibility)
 			{
@@ -2357,7 +2364,7 @@ namespace ICSharpCode.Decompiler.CSharp.Syntax
 				case Accessibility.ProtectedOrInternal:
 					return Modifiers.Protected | Modifiers.Internal;
 				case Accessibility.ProtectedAndInternal:
-					return Modifiers.Private | Modifiers.Protected;
+					return usePrivateProtected ? Modifiers.Private | Modifiers.Protected : Modifiers.Protected;
 				default:
 					return Modifiers.None;
 			}
@@ -2388,7 +2395,7 @@ namespace ICSharpCode.Decompiler.CSharp.Syntax
 			Modifiers m = Modifiers.None;
 			if (this.ShowAccessibility && NeedsAccessibility(member))
 			{
-				m |= ModifierFromAccessibility(member.Accessibility);
+				m |= ModifierFromAccessibility(member.Accessibility, UsePrivateProtectedAccessibility);
 			}
 			if (this.ShowModifiers)
 			{
diff --git a/ICSharpCode.Decompiler/DecompilerSettings.cs b/ICSharpCode.Decompiler/DecompilerSettings.cs
index b2d7f75e2..f4fac4485 100644
--- a/ICSharpCode.Decompiler/DecompilerSettings.cs
+++ b/ICSharpCode.Decompiler/DecompilerSettings.cs
@@ -112,6 +112,7 @@ namespace ICSharpCode.Decompiler
 				introduceRefModifiersOnStructs = false;
 				nonTrailingNamedArguments = false;
 				refExtensionMethods = false;
+				introducePrivateProtectedAccessibilty = false;
 			}
 			if (languageVersion < CSharp.LanguageVersion.CSharp7_3)
 			{
@@ -185,7 +186,7 @@ namespace ICSharpCode.Decompiler
 				|| patternBasedFixedStatement)
 				return CSharp.LanguageVersion.CSharp7_3;
 			if (introduceRefModifiersOnStructs || introduceReadonlyAndInModifiers
-				|| nonTrailingNamedArguments || refExtensionMethods)
+				|| nonTrailingNamedArguments || refExtensionMethods || introducePrivateProtectedAccessibilty)
 				return CSharp.LanguageVersion.CSharp7_2;
 			// C# 7.1 missing
 			if (outVariables || throwExpressions || tupleTypes || tupleConversions
@@ -1418,6 +1419,24 @@ namespace ICSharpCode.Decompiler
 			}
 		}
 
+		bool introducePrivateProtectedAccessibilty = true;
+
+		/// <summary>
+		/// Gets/Sets whether "private protected" should be used.
+		/// </summary>
+		[Category("C# 7.2 / VS 2017.4")]
+		[Description("DecompilerSettings.IntroducePrivateProtectedAccessibility")]
+		public bool IntroducePrivateProtectedAccessibility {
+			get { return introducePrivateProtectedAccessibilty; }
+			set {
+				if (introducePrivateProtectedAccessibilty != value)
+				{
+					introducePrivateProtectedAccessibilty = value;
+					OnPropertyChanged();
+				}
+			}
+		}
+
 		bool readOnlyMethods = true;
 
 		[Category("C# 8.0 / VS 2019")]
diff --git a/ICSharpCode.Decompiler/Output/IAmbience.cs b/ICSharpCode.Decompiler/Output/IAmbience.cs
index 6518313a4..6099678e0 100644
--- a/ICSharpCode.Decompiler/Output/IAmbience.cs
+++ b/ICSharpCode.Decompiler/Output/IAmbience.cs
@@ -117,9 +117,14 @@ namespace ICSharpCode.Decompiler.Output
 		/// Support C# 11 <c>operator checked</c>.
 		/// </summary>
 		SupportOperatorChecked = 0x100000,
+		/// <summary>
+		/// Support C# 7.2 <c>private protected</c>.
+		/// </summary>
+		UsePrivateProtectedAccessibility = 0x200000,
 
 		StandardConversionFlags = ShowParameterNames |
 			ShowAccessibility |
+			UsePrivateProtectedAccessibility |
 			ShowParameterList |
 			ShowParameterModifiers |
 			ShowParameterDefaultValues |
diff --git a/ILSpy/Properties/Resources.Designer.cs b/ILSpy/Properties/Resources.Designer.cs
index ce6597717..c2982ddda 100644
--- a/ILSpy/Properties/Resources.Designer.cs
+++ b/ILSpy/Properties/Resources.Designer.cs
@@ -1080,6 +1080,15 @@ namespace ICSharpCode.ILSpy.Properties {
             }
         }
         
+        /// <summary>
+        ///   Looks up a localized string similar to Introduce &apos;private protected&apos; accessibility.
+        /// </summary>
+        public static string DecompilerSettings_IntroducePrivateProtectedAccessibility {
+            get {
+                return ResourceManager.GetString("DecompilerSettings.IntroducePrivateProtectedAccessibility", resourceCulture);
+            }
+        }
+        
         /// <summary>
         ///   Looks up a localized string similar to Introduce static local functions.
         /// </summary>
@@ -1118,6 +1127,16 @@ namespace ICSharpCode.ILSpy.Properties {
             }
         }
         
+        /// <summary>
+        ///   Looks up a localized resource of type System.Object.
+        /// </summary>
+        public static object DecompilerSettings_LifetimeAnnotations {
+            get {
+                object obj = ResourceManager.GetObject("DecompilerSettings.LifetimeAnnotations", resourceCulture);
+                return ((object)(obj));
+            }
+        }
+        
         /// <summary>
         ///   Looks up a localized string similar to Use nint/nuint types.
         /// </summary>
diff --git a/ILSpy/Properties/Resources.resx b/ILSpy/Properties/Resources.resx
index dd575638a..8b6e4ce23 100644
--- a/ILSpy/Properties/Resources.resx
+++ b/ILSpy/Properties/Resources.resx
@@ -595,7 +595,7 @@ Are you sure you want to continue?</value>
     <value>Enable folding on all blocks in braces</value>
   </data>
   <data name="EnableSmoothScrolling" xml:space="preserve">
-	  <value>Enable smooth scrolling</value>
+    <value>Enable smooth scrolling</value>
   </data>
   <data name="EnableWordWrap" xml:space="preserve">
     <value>Enable word wrap</value>
@@ -1108,4 +1108,7 @@ Do you want to continue?</value>
   <data name="_Window" xml:space="preserve">
     <value>_Window</value>
   </data>
+  <data name="DecompilerSettings.IntroducePrivateProtectedAccessibility" xml:space="preserve">
+    <value>Introduce 'private protected' accessibility</value>
+  </data>
 </root>
\ No newline at end of file
diff --git a/ILSpy/Properties/Resources.zh-Hans.resx b/ILSpy/Properties/Resources.zh-Hans.resx
index 472ac2503..0c6decb07 100644
--- a/ILSpy/Properties/Resources.zh-Hans.resx
+++ b/ILSpy/Properties/Resources.zh-Hans.resx
@@ -369,6 +369,9 @@
   <data name="DecompilerSettings.IntroduceLocalFunctions" xml:space="preserve">
     <value>引入局部函数(local functions)</value>
   </data>
+  <data name="DecompilerSettings.IntroducePrivateProtectedAccessibility" xml:space="preserve">
+    <value />
+  </data>
   <data name="DecompilerSettings.IntroduceStaticLocalFunctions" xml:space="preserve">
     <value>引入静态局部函数(static local functions)</value>
   </data>

From f5e851240d5d0dfcc9a7ed02e108910353821c7d Mon Sep 17 00:00:00 2001
From: Siegfried Pammer <siegfriedpammer@gmail.com>
Date: Sat, 22 Mar 2025 14:59:05 +0100
Subject: [PATCH 49/49] Add missing null check

---
 ILSpy/ExtensionMethods.cs | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/ILSpy/ExtensionMethods.cs b/ILSpy/ExtensionMethods.cs
index 60bb77d40..f0d3e8f14 100644
--- a/ILSpy/ExtensionMethods.cs
+++ b/ILSpy/ExtensionMethods.cs
@@ -79,7 +79,7 @@ namespace ICSharpCode.ILSpy
 		public static ICompilation? GetTypeSystemWithCurrentOptionsOrNull(this MetadataFile file, SettingsService settingsService, LanguageVersion languageVersion)
 		{
 			var decompilerSettings = settingsService.DecompilerSettings.Clone();
-			if (!Enum.TryParse(languageVersion.Version, out Decompiler.CSharp.LanguageVersion csharpLanguageVersion))
+			if (!Enum.TryParse(languageVersion?.Version, out Decompiler.CSharp.LanguageVersion csharpLanguageVersion))
 				csharpLanguageVersion = Decompiler.CSharp.LanguageVersion.Latest;
 			decompilerSettings.SetLanguageVersion(csharpLanguageVersion);
 			return file