From 1d763dcd8579ce090f3211e69e87c95f6b6fdd60 Mon Sep 17 00:00:00 2001 From: JayashreeSF3546 Date: Wed, 12 Aug 2026 16:55:45 +0530 Subject: [PATCH 1/2] [Tracking] Update Test accessor to provide access to compiler generated fields on auto properties. --- .../tests/TestUtilities/TestAccessor.cs | 12 ++++ .../System.Windows.Forms/TestAccessorTests.cs | 63 +++++++++++++++++++ 2 files changed, 75 insertions(+) diff --git a/src/Common/tests/TestUtilities/TestAccessor.cs b/src/Common/tests/TestUtilities/TestAccessor.cs index 8230ef9ee42..8751c0c10c4 100644 --- a/src/Common/tests/TestUtilities/TestAccessor.cs +++ b/src/Common/tests/TestUtilities/TestAccessor.cs @@ -222,6 +222,12 @@ public override bool TryGetMember(GetMemberBinder binder, out object? result) memberName, BindingFlags.Static | BindingFlags.Instance | BindingFlags.NonPublic); + // Look for the compiler-generated backing field of an auto property. + if (info is null && type is not null) + { + info = GetAutoPropertyBackingField(type, memberName); + } + if (info is not null || type == typeof(object)) { // Found something, or already at the top of the type hierarchy @@ -235,5 +241,11 @@ public override bool TryGetMember(GetMemberBinder binder, out object? result) return info; } + + private static FieldInfo? GetAutoPropertyBackingField(Type type, string propertyName) + { + string backingFieldName = $"<{propertyName}>k__BackingField"; + return type.GetField(backingFieldName, BindingFlags.Static | BindingFlags.Instance | BindingFlags.NonPublic); + } } } diff --git a/src/test/unit/System.Windows.Forms/TestAccessorTests.cs b/src/test/unit/System.Windows.Forms/TestAccessorTests.cs index f19de0cc377..42062e10712 100644 --- a/src/test/unit/System.Windows.Forms/TestAccessorTests.cs +++ b/src/test/unit/System.Windows.Forms/TestAccessorTests.cs @@ -178,6 +178,69 @@ public void TestAccessor_DynamicAccess_BaseClassMethod() Assert.Equal(42, (int)accessor.AMethod()); } + [Fact] + public void TestAccessor_CanReadAutoPropertyBackingField() + { + AutoPropertyClass obj = new() + { + Count = 10 + }; + + var accessor = new TestAccessor(obj); + int value = accessor.Dynamic.Count; + Assert.Equal(10, value); + } + + [Fact] + public void TestAccessor_CanWriteAutoPropertyBackingField() + { + AutoPropertyClass obj = new(); + var accessor = new TestAccessor(obj); + accessor.Dynamic.Count = 25; + Assert.Equal(25, obj.Count); + } + + [Fact] + public void TestAccessor_CanRead_ReadOnlyAutoProperty() + { + ReadOnlyClass obj = new(); + var accessor = new TestAccessor(obj); + int value = accessor.Dynamic.Count; + Assert.Equal(5, value); + } + + [Fact] + public void TestAccessor_FindsInheritedAutoProperty() + { + DerivedClass obj = new() + { + Count = 15 + }; + + var accessor = new TestAccessor(obj); + int value = accessor.Dynamic.Count; + Assert.Equal(15, value); + } + + private class AutoPropertyClass + { + public int Count { get; set; } + } + + private class ReadOnlyClass + { + public int Count { get; } = 5; + } + + private class BaseClass + { + public int Count { get; set; } + } + + private class DerivedClass : BaseClass + { + } + // As you can't use a ref struct as a generic parameter to Action/Func, you // need to use a defined delegate to access an internal method that takes // or returns a ref struct (such as Spans). From 36206c41c40c4d081a54a59b8f87ef880fdd8e47 Mon Sep 17 00:00:00 2001 From: JayashreeSF3546 Date: Fri, 28 Aug 2026 17:34:43 +0530 Subject: [PATCH 2/2] Fix TestAccessor auto-property backing field access to preserve public-member restriction The original implementation fell back to the compiler-generated backing field whenever GetProperty(NonPublic) returned null. Since GetProperty with only the NonPublic flag only fails to match fully-public properties, this fallback only ever engaged for public auto-properties - directly violating TestAccessor's documented invariant that it must not expose public members (verified by TestAccessor_DynamicAccess_PublicProperty, which regressed to a failing state). Move the backing-field fallback into TrySetMember, triggered only when a non-public property is found via reflection but has no setter (e.g. get-only auto-properties). This preserves the public-member restriction while still enabling the real motivating scenario: writing to a private/ internal read-only auto-property for test setup. Updated the accompanying tests to use non-public auto-properties so they correctly exercise the new fallback path instead of relying on public members, which the accessor is designed to reject. --- .../tests/TestUtilities/TestAccessor.cs | 15 +++--- .../System.Windows.Forms/TestAccessorTests.cs | 50 +++++++++---------- 2 files changed, 31 insertions(+), 34 deletions(-) diff --git a/src/Common/tests/TestUtilities/TestAccessor.cs b/src/Common/tests/TestUtilities/TestAccessor.cs index 8751c0c10c4..d5591b8eb72 100644 --- a/src/Common/tests/TestUtilities/TestAccessor.cs +++ b/src/Common/tests/TestUtilities/TestAccessor.cs @@ -164,9 +164,16 @@ public override bool TrySetMember(SetMemberBinder binder, object? value) case FieldInfo fieldInfo: fieldInfo.SetValue(_instance, value); break; - case PropertyInfo propertyInfo: + case PropertyInfo { CanWrite: true } propertyInfo: propertyInfo.SetValue(_instance, value); break; + case PropertyInfo propertyInfo: + // Non-public auto property without a setter (e.g. get-only). Fall back to the + // compiler-generated backing field so tests can still set up state directly. + FieldInfo backingField = GetAutoPropertyBackingField(propertyInfo.DeclaringType!, propertyInfo.Name) + ?? throw new InvalidOperationException($"'{propertyInfo.Name}' has no setter and no backing field could be found."); + backingField.SetValue(_instance, value); + break; default: throw new InvalidOperationException(); } @@ -222,12 +229,6 @@ public override bool TryGetMember(GetMemberBinder binder, out object? result) memberName, BindingFlags.Static | BindingFlags.Instance | BindingFlags.NonPublic); - // Look for the compiler-generated backing field of an auto property. - if (info is null && type is not null) - { - info = GetAutoPropertyBackingField(type, memberName); - } - if (info is not null || type == typeof(object)) { // Found something, or already at the top of the type hierarchy diff --git a/src/test/unit/System.Windows.Forms/TestAccessorTests.cs b/src/test/unit/System.Windows.Forms/TestAccessorTests.cs index 42062e10712..2c9bcf48f5a 100644 --- a/src/test/unit/System.Windows.Forms/TestAccessorTests.cs +++ b/src/test/unit/System.Windows.Forms/TestAccessorTests.cs @@ -179,67 +179,63 @@ public void TestAccessor_DynamicAccess_BaseClassMethod() } [Fact] - public void TestAccessor_CanReadAutoPropertyBackingField() - { - AutoPropertyClass obj = new() - { - Count = 10 - }; - - var accessor = new TestAccessor(obj); - int value = accessor.Dynamic.Count; - Assert.Equal(10, value); - } - - [Fact] - public void TestAccessor_CanWriteAutoPropertyBackingField() + public void TestAccessor_CanReadAndWrite_NonPublicAutoProperty() { AutoPropertyClass obj = new(); var accessor = new TestAccessor(obj); + accessor.Dynamic.Count = 25; - Assert.Equal(25, obj.Count); + int value = accessor.Dynamic.Count; + Assert.Equal(25, value); } [Fact] - public void TestAccessor_CanRead_ReadOnlyAutoProperty() + public void TestAccessor_CanWrite_NonPublicReadOnlyAutoProperty_ViaBackingField() { + // Count has no setter, so writing it can only be accomplished by falling back to the + // compiler-generated backing field. ReadOnlyClass obj = new(); var accessor = new TestAccessor(obj); - int value = accessor.Dynamic.Count; - Assert.Equal(5, value); + + int initial = accessor.Dynamic.Count; + Assert.Equal(5, initial); + + accessor.Dynamic.Count = 42; + int updated = accessor.Dynamic.Count; + Assert.Equal(42, updated); } [Fact] - public void TestAccessor_FindsInheritedAutoProperty() + public void TestAccessor_FindsInherited_NonPublicReadOnlyAutoPropertyBackingField() { - DerivedClass obj = new() - { - Count = 15 - }; - + DerivedClass obj = new(); var accessor = new TestAccessor(obj); + + accessor.Dynamic.Count = 15; int value = accessor.Dynamic.Count; Assert.Equal(15, value); } +#pragma warning disable IDE0051 // unaccessed private private class AutoPropertyClass { - public int Count { get; set; } + private int Count { get; set; } } private class ReadOnlyClass { - public int Count { get; } = 5; + private int Count { get; } = 5; } private class BaseClass { - public int Count { get; set; } + private int Count { get; } } private class DerivedClass : BaseClass { } +#pragma warning restore IDE0051 // As you can't use a ref struct as a generic parameter to Action/Func, you // need to use a defined delegate to access an internal method that takes