From 2d2e9c15efe61130914b816eec9e09619ae53e3e Mon Sep 17 00:00:00 2001 From: Sebastien Lebreton Date: Tue, 11 Aug 2026 09:07:55 +0200 Subject: [PATCH] Keep instance-dependent assignments in constructors A moved field initializer cannot read another instance member. Reject primary-constructor conversion for that case and preserve the original constructor. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: 0dd407b6-9410-48df-add5-761ca4a8dec0 --- .../Pretty/ConstructorInitializers.cs | 13 +++++++++++++ ...ransformFieldAndConstructorInitializers.cs | 19 +++++++++++++++++++ 2 files changed, 32 insertions(+) diff --git a/ICSharpCode.Decompiler.Tests/TestCases/Pretty/ConstructorInitializers.cs b/ICSharpCode.Decompiler.Tests/TestCases/Pretty/ConstructorInitializers.cs index 6d2d4041a..c4b3f3f6f 100644 --- a/ICSharpCode.Decompiler.Tests/TestCases/Pretty/ConstructorInitializers.cs +++ b/ICSharpCode.Decompiler.Tests/TestCases/Pretty/ConstructorInitializers.cs @@ -134,6 +134,19 @@ namespace ICSharpCode.Decompiler.Tests.TestCases.Pretty public int B = b; } + + private struct StructInitializerDependsOnField + { + public int A; + + public int B; + + public StructInitializerDependsOnField(int value) + { + A = value; + B = A + 1; + } + } #endif public class ClassWithConstant diff --git a/ICSharpCode.Decompiler/CSharp/Transforms/TransformFieldAndConstructorInitializers.cs b/ICSharpCode.Decompiler/CSharp/Transforms/TransformFieldAndConstructorInitializers.cs index 63da3e010..09af9bbfc 100644 --- a/ICSharpCode.Decompiler/CSharp/Transforms/TransformFieldAndConstructorInitializers.cs +++ b/ICSharpCode.Decompiler/CSharp/Transforms/TransformFieldAndConstructorInitializers.cs @@ -356,6 +356,7 @@ namespace ICSharpCode.Decompiler.CSharp.Transforms var initializer = InitializerSequence.Analyze(this, ctor, ctorMethod); if (initializer is { CoversFullBody: true, HasDuplicateAssignments: false, Statements.Count: > 0 } + && !initializer.Statements.Any(statement => ReferencesInstanceMember(statement.Initializer)) && ctorMethod.Accessibility == expectedCtorAccessibility) { bool transformToPrimaryConstructor = MetadataTokens.GetRowNumber(ctorMethod.MetadataToken) == firstMethodRowNumber; @@ -403,6 +404,24 @@ namespace ICSharpCode.Decompiler.CSharp.Transforms } } + bool ReferencesInstanceMember(Expression expression) + { + foreach (var node in expression.DescendantsAndSelf.OfType()) + { + switch (node.GetResolveResult()) + { + case ThisResolveResult: + return true; + case MemberResolveResult { + TargetResult: ThisResolveResult, + Member: { IsStatic: false } member + } when member is not IField field || !IsGeneratedPrimaryConstructorBackingField(field): + return true; + } + } + return false; + } + if (StaticConstructor != null) { StaticInitializers = InitializerSequence.Analyze(this, StaticConstructorDecl!, StaticConstructor);