diff --git a/CodeConverter/CSharp/CommonConversions.cs b/CodeConverter/CSharp/CommonConversions.cs index 6647937f..8b24d499 100644 --- a/CodeConverter/CSharp/CommonConversions.cs +++ b/CodeConverter/CSharp/CommonConversions.cs @@ -1,4 +1,4 @@ -using System.Collections.Immutable; +using System.Collections.Immutable; using System.Linq.Expressions; using System.Runtime.CompilerServices; using ICSharpCode.CodeConverter.Util.FromRoslyn; @@ -293,7 +293,12 @@ public SyntaxToken ConvertIdentifier(SyntaxToken id, bool isAttribute = false, S // AND the first explicitly declared parameter is this symbol, we need to replace it with value. text = "value"; } else if (normalizedText.StartsWith("_", StringComparison.OrdinalIgnoreCase) && idSymbol is IFieldSymbol propertyFieldSymbol && propertyFieldSymbol.AssociatedSymbol?.IsKind(SymbolKind.Property) == true) { - text = propertyFieldSymbol.AssociatedSymbol.Name; + // For virtual auto-properties, VB backing field _Prop maps to the C# MyClassProp backing property (bypasses virtual dispatch). + // Exception: when accessed as MyClass._Prop, NameExpressionNodeVisitor adds the "MyClass" prefix itself, so we just return the property name. + var isAccessedViaMyClass = id.Parent?.Parent is VBSyntax.MemberAccessExpressionSyntax { Expression: VBSyntax.MyClassExpressionSyntax }; + text = !isAccessedViaMyClass && propertyFieldSymbol.IsImplicitlyDeclared && propertyFieldSymbol.AssociatedSymbol is IPropertySymbol { IsVirtual: true, IsAbstract: false } vProp + ? "MyClass" + vProp.Name + : propertyFieldSymbol.AssociatedSymbol.Name; } else if (normalizedText.EndsWith("Event", StringComparison.OrdinalIgnoreCase) && idSymbol is IFieldSymbol eventFieldSymbol && eventFieldSymbol.AssociatedSymbol?.IsKind(SymbolKind.Event) == true) { text = eventFieldSymbol.AssociatedSymbol.Name; } else if (WinformsConversions.MayNeedToInlinePropertyAccess(id.Parent, idSymbol) && _typeContext.HandledEventsAnalysis.ShouldGeneratePropertyFor(idSymbol.Name)) { diff --git a/CodeConverter/CSharp/DeclarationNodeVisitor.cs b/CodeConverter/CSharp/DeclarationNodeVisitor.cs index f6fd6709..c7d3e72a 100644 --- a/CodeConverter/CSharp/DeclarationNodeVisitor.cs +++ b/CodeConverter/CSharp/DeclarationNodeVisitor.cs @@ -689,12 +689,25 @@ private static async Task ConvertStatementsAsync(SyntaxList (IEnumerable) await s.Accept(methodBodyVisitor))); } - private static HashSet GetMyClassAccessedNames(VBSyntax.ClassBlockSyntax classBlock) + private HashSet GetMyClassAccessedNames(VBSyntax.ClassBlockSyntax classBlock) { var memberAccesses = classBlock.DescendantNodes().OfType(); var accessedTextNames = new HashSet(memberAccesses .Where(mae => mae.Expression is VBSyntax.MyClassExpressionSyntax) .Select(mae => mae.Name.Identifier.Text), StringComparer.OrdinalIgnoreCase); + + // Also treat direct backing field access (_Prop) as MyClass access for virtual auto-properties. + // In VB, writing _Prop directly accesses the backing field, bypassing virtual dispatch - + // the same semantics as MyClass.Prop. In C#, these virtual properties get a MyClassProp + // backing property, so _Prop must map to MyClassProp. + var backingFieldIdentifiers = classBlock.DescendantNodes().OfType() + .Where(id => id.Identifier.ValueText.StartsWith("_", StringComparison.OrdinalIgnoreCase)); + foreach (var id in backingFieldIdentifiers) { + if (_semanticModel.GetSymbolInfo(id).Symbol is IFieldSymbol { IsImplicitlyDeclared: true, AssociatedSymbol: IPropertySymbol { IsVirtual: true, IsAbstract: false } associatedProp }) { + accessedTextNames.Add(associatedProp.Name); + } + } + return accessedTextNames; } diff --git a/Tests/CSharp/MemberTests/PropertyMemberTests.cs b/Tests/CSharp/MemberTests/PropertyMemberTests.cs index 790859d7..8202496b 100644 --- a/Tests/CSharp/MemberTests/PropertyMemberTests.cs +++ b/Tests/CSharp/MemberTests/PropertyMemberTests.cs @@ -1,4 +1,4 @@ -using System.Threading.Tasks; +using System.Threading.Tasks; using ICSharpCode.CodeConverter.Tests.TestRunners; using Xunit; @@ -874,4 +874,49 @@ public static IEnumerable SomeObjects } }"); } -} \ No newline at end of file + /// Issue #827: VB auto-property backing field access (_Prop) should map to MyClassProp for overridable properties + [Fact] + public async Task TestOverridableAutoPropertyBackingFieldAccessAsync() + { + await TestConversionVisualBasicToCSharpAsync(@"Class Foo + Overridable Property Prop As Integer = 5 + + Sub Test() + _Prop = 10 + Dim isCorrect = MyClass.Prop = 10 + End Sub +End Class +Class Child + Inherits Foo + Overrides Property Prop As Integer = 20 +End Class", @" +internal partial class Foo +{ + public int MyClassProp { get; set; } = 5; + + public virtual int Prop + { + get + { + return MyClassProp; + } + + set + { + MyClassProp = value; + } + } + + public void Test() + { + MyClassProp = 10; + bool isCorrect = MyClassProp == 10; + } +} + +internal partial class Child : Foo +{ + public override int Prop { get; set; } = 20; +}"); + } +} diff --git a/Tests/VB/MemberTests.cs b/Tests/VB/MemberTests.cs index 03bd4dd0..570e5cd8 100644 --- a/Tests/VB/MemberTests.cs +++ b/Tests/VB/MemberTests.cs @@ -1,4 +1,4 @@ -using System.Threading.Tasks; +using System.Threading.Tasks; using ICSharpCode.CodeConverter.Tests.TestRunners; using ICSharpCode.CodeConverter.VB; using Xunit; @@ -1495,4 +1495,4 @@ End Sub End Class"); } -} \ No newline at end of file +}