From 6f0b75bf277a48fda8e80c2950eff971d55815fb Mon Sep 17 00:00:00 2001 From: Steve Pfister Date: Mon, 11 May 2026 20:45:48 -0400 Subject: [PATCH 1/6] Fix ConstructorInfo.GetGenericArguments to return empty array Constructors are never generic methods in the CLR (there is no IL or language support for generic constructors). Previously, calling GetGenericArguments() on a ConstructorInfo threw NotSupportedException because the call fell through to the base MethodBase implementation, which throws by design when not overridden. RuntimeMethodInfo returns an empty array for non-generic methods, but RuntimeConstructorInfo did not override the method and so threw. Add a single override on the abstract ConstructorInfo base so the fix applies uniformly to all subclasses (CoreCLR, Mono, NativeAOT, MetadataLoadContext, custom ConstructorInfo subclasses, etc.). Fixes #128041 Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .../src/System/Reflection/ConstructorInfo.cs | 3 +++ .../ConstructorInfoTests.cs | 16 ++++++++++++++++ 2 files changed, 19 insertions(+) diff --git a/src/libraries/System.Private.CoreLib/src/System/Reflection/ConstructorInfo.cs b/src/libraries/System.Private.CoreLib/src/System/Reflection/ConstructorInfo.cs index a680267e160719..5a76b17d8d1828 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Reflection/ConstructorInfo.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Reflection/ConstructorInfo.cs @@ -13,6 +13,9 @@ protected ConstructorInfo() { } public override MemberTypes MemberType => MemberTypes.Constructor; + // Constructors are never generic methods, so the generic method arguments are always empty. + public override Type[] GetGenericArguments() => []; + [DebuggerHidden] [DebuggerStepThrough] public object Invoke(object?[]? parameters) => Invoke(BindingFlags.Default, binder: null, parameters: parameters, culture: null); diff --git a/src/libraries/System.Runtime/tests/System.Reflection.Tests/ConstructorInfoTests.cs b/src/libraries/System.Runtime/tests/System.Reflection.Tests/ConstructorInfoTests.cs index 37498347ac48fe..29cf5ce70dd370 100644 --- a/src/libraries/System.Runtime/tests/System.Reflection.Tests/ConstructorInfoTests.cs +++ b/src/libraries/System.Runtime/tests/System.Reflection.Tests/ConstructorInfoTests.cs @@ -133,6 +133,16 @@ public void IsPublic() Assert.True(constructors[0].IsPublic); } + [Fact] + public void GetGenericArguments_ReturnsEmptyArray() + { + ConstructorInfo[] constructors = GetConstructors(typeof(ClassWith3Constructors)); + Assert.All(constructors, constructorInfo => Assert.Empty(constructorInfo.GetGenericArguments())); + + ConstructorInfo[] genericTypeConstructors = GetConstructors(typeof(GenericClassWithConstructor)); + Assert.All(genericTypeConstructors, constructorInfo => Assert.Empty(constructorInfo.GetGenericArguments())); + } + // Use this class only from the Invoke_StaticConstructorMultipleTimes method public static class ClassWithStaticConstructorThatIsCalledMultipleTimesViaReflection { @@ -159,6 +169,12 @@ public class ConstructorInfoDerived : ConstructorInfoAbstractBase public ConstructorInfoDerived() { } } + public class GenericClassWithConstructor + { + public GenericClassWithConstructor() { } + public GenericClassWithConstructor(T value) { } + } + public class ClassWith3Constructors { public int intValue = 0; From 31a2f19130a7982dea432c7226825d896c58a884 Mon Sep 17 00:00:00 2001 From: Steve Pfister Date: Tue, 12 May 2026 07:01:07 -0400 Subject: [PATCH 2/6] Update NativeAOT and existing tests for new ConstructorInfo behavior NativeAOT previously overrode ConstructorInfo.GetGenericArguments() to throw NotSupportedException for desktop compat. Remove that override so the new ConstructorInfo base implementation (returns empty array) is used uniformly. Update two tests that asserted the prior throw behavior to assert the new empty-array contract: - System.Reflection.Context CustomConstructorInfo test - System.Reflection.MetadataLoadContext invariant test (TestConstructorInfoCommonInvariants is the upstream of several failing tests in TypeTests and ConstructorTests) Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .../Runtime/MethodInfos/RuntimeConstructorInfo.cs | 6 ------ .../tests/CustomConstructorInfoTests.cs | 6 +++--- .../tests/src/Tests/Method/MethodInvariants.cs | 2 +- 3 files changed, 4 insertions(+), 10 deletions(-) diff --git a/src/coreclr/nativeaot/System.Private.CoreLib/src/System/Reflection/Runtime/MethodInfos/RuntimeConstructorInfo.cs b/src/coreclr/nativeaot/System.Private.CoreLib/src/System/Reflection/Runtime/MethodInfos/RuntimeConstructorInfo.cs index 5fc19889cb9e91..1ed8e75c7d7d82 100644 --- a/src/coreclr/nativeaot/System.Private.CoreLib/src/System/Reflection/Runtime/MethodInfos/RuntimeConstructorInfo.cs +++ b/src/coreclr/nativeaot/System.Private.CoreLib/src/System/Reflection/Runtime/MethodInfos/RuntimeConstructorInfo.cs @@ -32,12 +32,6 @@ public sealed override bool ContainsGenericParameters public abstract override Type DeclaringType { get; } - public sealed override Type[] GetGenericArguments() - { - // Constructors cannot be generic. Desktop compat dictates that We throw NotSupported rather than returning a 0-length array. - throw new NotSupportedException(); - } - [RequiresUnreferencedCode("Trimming may change method bodies. For example it can change some instructions, remove branches or local variables.")] public sealed override MethodBody GetMethodBody() { diff --git a/src/libraries/System.Reflection.Context/tests/CustomConstructorInfoTests.cs b/src/libraries/System.Reflection.Context/tests/CustomConstructorInfoTests.cs index ceb4b7ace553f6..9dc2886c0426b3 100644 --- a/src/libraries/System.Reflection.Context/tests/CustomConstructorInfoTests.cs +++ b/src/libraries/System.Reflection.Context/tests/CustomConstructorInfoTests.cs @@ -142,10 +142,10 @@ public void IsDefined_ReturnsFalseForUnattributedConstructor() } [Fact] - public void GetGenericArguments_ThrowsNotSupported() + public void GetGenericArguments_ReturnsEmpty() { - // Constructors don't support GetGenericArguments - Assert.Throws(() => _customConstructor.GetGenericArguments()); + // Constructors are never generic methods, so GetGenericArguments returns an empty array. + Assert.Empty(_customConstructor.GetGenericArguments()); } [ConditionalFact(typeof(PlatformDetection), nameof(PlatformDetection.IsMethodBodySupported))] diff --git a/src/libraries/System.Reflection.MetadataLoadContext/tests/src/Tests/Method/MethodInvariants.cs b/src/libraries/System.Reflection.MetadataLoadContext/tests/src/Tests/Method/MethodInvariants.cs index 8e8a1fbdebb6d1..8da14ed5191973 100644 --- a/src/libraries/System.Reflection.MetadataLoadContext/tests/src/Tests/Method/MethodInvariants.cs +++ b/src/libraries/System.Reflection.MetadataLoadContext/tests/src/Tests/Method/MethodInvariants.cs @@ -106,7 +106,7 @@ private static void TestConstructorInfoCommonInvariants(this ConstructorInfo c) Assert.False(c.IsConstructedGenericMethod()); Assert.False(c.IsGenericMethod); - Assert.Throws(() => c.GetGenericArguments()); + Assert.Empty(c.GetGenericArguments()); } private static void TestMethodInfoCommonInvariants(this MethodInfo m) From 7f1de4f23822699c6fef51f50c5c772ab7c8d207 Mon Sep 17 00:00:00 2001 From: Steve Pfister Date: Tue, 12 May 2026 12:18:44 -0400 Subject: [PATCH 3/6] Conditionalize MLC constructor invariant for .NET Framework The System.Reflection.MetadataLoadContext tests target both NetCoreAppCurrent and NetFrameworkCurrent. On .NET Framework, ConstructorInfo.GetGenericArguments() still throws NotSupportedException (this fix only ships in .NET Core). Guard the invariant check with #if NET so the test asserts the appropriate behavior on each runtime. This addresses the remaining failures in the Libraries_NET481 leg: TestConstructors1, TestInvariantCode, TestMakeArray, TestInvariants, TestMakeGenericType, and TestMakeMdArray (all upstream-rooted in TestConstructorInfoCommonInvariants). Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .../tests/src/Tests/Method/MethodInvariants.cs | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/libraries/System.Reflection.MetadataLoadContext/tests/src/Tests/Method/MethodInvariants.cs b/src/libraries/System.Reflection.MetadataLoadContext/tests/src/Tests/Method/MethodInvariants.cs index 8da14ed5191973..dbde3a65d98f7f 100644 --- a/src/libraries/System.Reflection.MetadataLoadContext/tests/src/Tests/Method/MethodInvariants.cs +++ b/src/libraries/System.Reflection.MetadataLoadContext/tests/src/Tests/Method/MethodInvariants.cs @@ -106,7 +106,12 @@ private static void TestConstructorInfoCommonInvariants(this ConstructorInfo c) Assert.False(c.IsConstructedGenericMethod()); Assert.False(c.IsGenericMethod); +#if NET Assert.Empty(c.GetGenericArguments()); +#else + // On .NET Framework, ConstructorInfo.GetGenericArguments() throws NotSupportedException. + Assert.Throws(() => c.GetGenericArguments()); +#endif } private static void TestMethodInfoCommonInvariants(this MethodInfo m) From 46517c917a9bbf5bde6cff4725dce8e33c423fb7 Mon Sep 17 00:00:00 2001 From: Steve Pfister Date: Tue, 12 May 2026 12:28:00 -0400 Subject: [PATCH 4/6] Address PR feedback: also verify open generic type constructors Per @max-charlamb's suggestion, add coverage for an open generic type (GenericClassWithConstructor<>) in addition to the closed one. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .../tests/System.Reflection.Tests/ConstructorInfoTests.cs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/libraries/System.Runtime/tests/System.Reflection.Tests/ConstructorInfoTests.cs b/src/libraries/System.Runtime/tests/System.Reflection.Tests/ConstructorInfoTests.cs index 29cf5ce70dd370..e8d791d2f8a19f 100644 --- a/src/libraries/System.Runtime/tests/System.Reflection.Tests/ConstructorInfoTests.cs +++ b/src/libraries/System.Runtime/tests/System.Reflection.Tests/ConstructorInfoTests.cs @@ -141,6 +141,9 @@ public void GetGenericArguments_ReturnsEmptyArray() ConstructorInfo[] genericTypeConstructors = GetConstructors(typeof(GenericClassWithConstructor)); Assert.All(genericTypeConstructors, constructorInfo => Assert.Empty(constructorInfo.GetGenericArguments())); + + ConstructorInfo[] openGenericTypeConstructors = GetConstructors(typeof(GenericClassWithConstructor<>)); + Assert.All(openGenericTypeConstructors, constructorInfo => Assert.Empty(constructorInfo.GetGenericArguments())); } // Use this class only from the Invoke_StaticConstructorMultipleTimes method From 4b00ab24ab06a11f47b3e5051ba554d66d6c9ece Mon Sep 17 00:00:00 2001 From: Steve Pfister Date: Tue, 12 May 2026 15:03:37 -0400 Subject: [PATCH 5/6] Add ConstructorInfo.GetGenericArguments override to reference assembly Per @jkotas's review, the public override on a public type needs to be declared in the reference assembly so it appears in the public API surface area. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- src/libraries/System.Runtime/ref/System.Runtime.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/src/libraries/System.Runtime/ref/System.Runtime.cs b/src/libraries/System.Runtime/ref/System.Runtime.cs index 60651667769b39..b84cfd8c2c63aa 100644 --- a/src/libraries/System.Runtime/ref/System.Runtime.cs +++ b/src/libraries/System.Runtime/ref/System.Runtime.cs @@ -12309,6 +12309,7 @@ public abstract partial class ConstructorInfo : System.Reflection.MethodBase protected ConstructorInfo() { } public override System.Reflection.MemberTypes MemberType { get { throw null; } } public override bool Equals(object? obj) { throw null; } + public override System.Type[] GetGenericArguments() { throw null; } public override int GetHashCode() { throw null; } public object Invoke(object?[]? parameters) { throw null; } public abstract object Invoke(System.Reflection.BindingFlags invokeAttr, System.Reflection.Binder? binder, object?[]? parameters, System.Globalization.CultureInfo? culture); From 65f4bb7577e591f5d9ed88d1cb0703705550e51b Mon Sep 17 00:00:00 2001 From: Steve Pfister Date: Tue, 12 May 2026 15:12:33 -0400 Subject: [PATCH 6/6] Remove redundant MemberType override from RuntimeConstructorInfo.CoreCLR Per @jkotas's review, the ConstructorInfo base class already defines MemberType => MemberTypes.Constructor (and exposes it in the reference assembly), so the override on RuntimeConstructorInfo.CoreCLR is unnecessary. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .../src/System/Reflection/RuntimeConstructorInfo.CoreCLR.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/src/coreclr/System.Private.CoreLib/src/System/Reflection/RuntimeConstructorInfo.CoreCLR.cs b/src/coreclr/System.Private.CoreLib/src/System/Reflection/RuntimeConstructorInfo.CoreCLR.cs index 0c5de0c21006a1..39da6d248bdd2c 100644 --- a/src/coreclr/System.Private.CoreLib/src/System/Reflection/RuntimeConstructorInfo.CoreCLR.cs +++ b/src/coreclr/System.Private.CoreLib/src/System/Reflection/RuntimeConstructorInfo.CoreCLR.cs @@ -159,7 +159,6 @@ public override IList GetCustomAttributesData() #region MemberInfo Overrides public override string Name => RuntimeMethodHandle.GetName(this); - public override MemberTypes MemberType => MemberTypes.Constructor; public override Type? DeclaringType => m_reflectedTypeCache.IsGlobal ? null : m_declaringType;