Skip to content
9 changes: 7 additions & 2 deletions CodeConverter/CSharp/CommonConversions.cs
Original file line number Diff line number Diff line change
@@ -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;
Expand Down Expand Up @@ -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)) {
Expand Down
15 changes: 14 additions & 1 deletion CodeConverter/CSharp/DeclarationNodeVisitor.cs
Original file line number Diff line number Diff line change
Expand Up @@ -689,12 +689,25 @@ private static async Task<BlockSyntax> ConvertStatementsAsync(SyntaxList<VBSynta
return CS.SyntaxFactory.Block(await statements.SelectManyAsync(async s => (IEnumerable<StatementSyntax>) await s.Accept(methodBodyVisitor)));
}

private static HashSet<string> GetMyClassAccessedNames(VBSyntax.ClassBlockSyntax classBlock)
private HashSet<string> GetMyClassAccessedNames(VBSyntax.ClassBlockSyntax classBlock)
{
var memberAccesses = classBlock.DescendantNodes().OfType<VBSyntax.MemberAccessExpressionSyntax>();
var accessedTextNames = new HashSet<string>(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<VBSyntax.IdentifierNameSyntax>()
.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;
}

Expand Down
49 changes: 47 additions & 2 deletions Tests/CSharp/MemberTests/PropertyMemberTests.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
using System.Threading.Tasks;
using System.Threading.Tasks;
using ICSharpCode.CodeConverter.Tests.TestRunners;
using Xunit;

Expand Down Expand Up @@ -874,4 +874,49 @@ public static IEnumerable<object[]> SomeObjects
}
}");
}
}
/// <summary>Issue #827: VB auto-property backing field access (_Prop) should map to MyClassProp for overridable properties</summary>
[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;
}");
}
}
4 changes: 2 additions & 2 deletions Tests/VB/MemberTests.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
using System.Threading.Tasks;
using System.Threading.Tasks;
using ICSharpCode.CodeConverter.Tests.TestRunners;
using ICSharpCode.CodeConverter.VB;
using Xunit;
Expand Down Expand Up @@ -1495,4 +1495,4 @@ End Sub
End Class");
}

}
}
Loading