From a7252b74a948c4bab9c2660684fb1fee67d37f7a Mon Sep 17 00:00:00 2001 From: Heath Baron-Morgan Date: Tue, 11 Oct 2022 09:44:26 -0700 Subject: [PATCH] Add non-generic Create method to DispatchProxy (#73046) Co-authored-by: Buyaa Namnan --- .../ref/System.Reflection.DispatchProxy.cs | 2 + .../src/Resources/Strings.resx | 7 +- .../src/System/Reflection/DispatchProxy.cs | 27 +- .../Reflection/DispatchProxyGenerator.cs | 23 +- .../tests/DispatchProxyTests.cs | 421 ++++++++++++------ .../tests/TestTypes.cs | 4 + 6 files changed, 329 insertions(+), 155 deletions(-) diff --git a/src/libraries/System.Reflection.DispatchProxy/ref/System.Reflection.DispatchProxy.cs b/src/libraries/System.Reflection.DispatchProxy/ref/System.Reflection.DispatchProxy.cs index 07bf6ea97b17c..6eb1b6797f61e 100644 --- a/src/libraries/System.Reflection.DispatchProxy/ref/System.Reflection.DispatchProxy.cs +++ b/src/libraries/System.Reflection.DispatchProxy/ref/System.Reflection.DispatchProxy.cs @@ -10,6 +10,8 @@ public abstract partial class DispatchProxy { protected DispatchProxy() { } [System.Diagnostics.CodeAnalysis.RequiresDynamicCodeAttribute("Creating a proxy instance requires generating code at runtime")] + public static object Create([System.Diagnostics.CodeAnalysis.DynamicallyAccessedMembersAttribute(System.Diagnostics.CodeAnalysis.DynamicallyAccessedMemberTypes.All)] System.Type interfaceType, [System.Diagnostics.CodeAnalysis.DynamicallyAccessedMembersAttribute(System.Diagnostics.CodeAnalysis.DynamicallyAccessedMemberTypes.PublicParameterlessConstructor)] System.Type proxyType) { throw null; } + [System.Diagnostics.CodeAnalysis.RequiresDynamicCodeAttribute("Creating a proxy instance requires generating code at runtime")] public static T Create<[System.Diagnostics.CodeAnalysis.DynamicallyAccessedMembersAttribute(System.Diagnostics.CodeAnalysis.DynamicallyAccessedMemberTypes.All)] T, [System.Diagnostics.CodeAnalysis.DynamicallyAccessedMembersAttribute(System.Diagnostics.CodeAnalysis.DynamicallyAccessedMemberTypes.PublicParameterlessConstructor)] TProxy>() where TProxy : System.Reflection.DispatchProxy { throw null; } protected abstract object? Invoke(System.Reflection.MethodInfo? targetMethod, object?[]? args); } diff --git a/src/libraries/System.Reflection.DispatchProxy/src/Resources/Strings.resx b/src/libraries/System.Reflection.DispatchProxy/src/Resources/Strings.resx index 207ef13683a04..73f8a5ed0f400 100644 --- a/src/libraries/System.Reflection.DispatchProxy/src/Resources/Strings.resx +++ b/src/libraries/System.Reflection.DispatchProxy/src/Resources/Strings.resx @@ -1,4 +1,4 @@ - + @@ -66,10 +66,13 @@ The type '{0}' must be an interface, not a class. - + The base type '{0}' cannot be abstract. This operation is not supported on .NET Standard as Reflection.Emit is not available. + + The provided proxy type '{0}' must be derived from 'DispatchProxy'. + \ No newline at end of file diff --git a/src/libraries/System.Reflection.DispatchProxy/src/System/Reflection/DispatchProxy.cs b/src/libraries/System.Reflection.DispatchProxy/src/System/Reflection/DispatchProxy.cs index 4382a38267608..eaf613dbd9c32 100644 --- a/src/libraries/System.Reflection.DispatchProxy/src/System/Reflection/DispatchProxy.cs +++ b/src/libraries/System.Reflection.DispatchProxy/src/System/Reflection/DispatchProxy.cs @@ -40,7 +40,32 @@ protected DispatchProxy() public static T Create<[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.All)] T, [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicParameterlessConstructor)] TProxy>() where TProxy : DispatchProxy { - return (T)DispatchProxyGenerator.CreateProxyInstance(typeof(TProxy), typeof(T)); + return (T)DispatchProxyGenerator.CreateProxyInstance(typeof(TProxy), typeof(T), "T", "TProxy"); + } + + /// + /// Creates an object instance that derives from class + /// and implements interface . + /// + /// The interface the proxy should implement. + /// The base class to use for the proxy class. + /// An object instance that implements . + /// or is null + /// is a class, + /// or is sealed or abstract or does not inherited from the + /// type or have a parameterless constructor + [RequiresDynamicCode("Creating a proxy instance requires generating code at runtime")] + public static object Create([DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.All)] Type interfaceType, [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicParameterlessConstructor)] Type proxyType) + { + ArgumentNullException.ThrowIfNull(interfaceType); + ArgumentNullException.ThrowIfNull(proxyType); + + if (!proxyType.IsAssignableTo(typeof(DispatchProxy))) + { + throw new ArgumentException(SR.Format(SR.ProxyType_Must_Be_Derived_From_DispatchProxy, proxyType.Name), nameof(proxyType)); + } + + return DispatchProxyGenerator.CreateProxyInstance(proxyType, interfaceType, "interfaceType", "proxyType"); } } } diff --git a/src/libraries/System.Reflection.DispatchProxy/src/System/Reflection/DispatchProxyGenerator.cs b/src/libraries/System.Reflection.DispatchProxy/src/System/Reflection/DispatchProxyGenerator.cs index 7b473738946b2..0ed13300da89c 100644 --- a/src/libraries/System.Reflection.DispatchProxy/src/System/Reflection/DispatchProxyGenerator.cs +++ b/src/libraries/System.Reflection.DispatchProxy/src/System/Reflection/DispatchProxyGenerator.cs @@ -62,7 +62,8 @@ private static MethodInfo GetGenericMethodMethodInfo() => [RequiresDynamicCode("Defining a dynamic assembly requires generating code at runtime")] internal static object CreateProxyInstance( [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicParameterlessConstructor)] Type baseType, - [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.All)] Type interfaceType) + [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.All)] Type interfaceType, + string interfaceParameter, string proxyParameter) { Debug.Assert(baseType != null); Debug.Assert(interfaceType != null); @@ -71,7 +72,7 @@ internal static object CreateProxyInstance( Debug.Assert(alc != null); ProxyAssembly proxyAssembly = s_alcProxyAssemblyMap.GetValue(alc, static x => new ProxyAssembly(x)); - GeneratedTypeInfo proxiedType = proxyAssembly.GetProxyType(baseType, interfaceType); + GeneratedTypeInfo proxiedType = proxyAssembly.GetProxyType(baseType, interfaceType, interfaceParameter, proxyParameter); return Activator.CreateInstance(proxiedType.GeneratedType, new object[] { proxiedType.MethodInfos })!; } @@ -141,7 +142,8 @@ public ProxyAssembly(AssemblyLoadContext alc) public GeneratedTypeInfo GetProxyType( [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicParameterlessConstructor)] Type baseType, - [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.All)] Type interfaceType) + [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.All)] Type interfaceType, + string interfaceParameter, string proxyParameter) { lock (_baseTypeAndInterfaceToGeneratedProxyType) { @@ -153,7 +155,7 @@ public GeneratedTypeInfo GetProxyType( if (!interfaceToProxy.TryGetValue(interfaceType, out GeneratedTypeInfo? generatedProxy)) { - generatedProxy = GenerateProxyType(baseType, interfaceType); + generatedProxy = GenerateProxyType(baseType, interfaceType, interfaceParameter, proxyParameter); interfaceToProxy[interfaceType] = generatedProxy; } @@ -166,7 +168,8 @@ public GeneratedTypeInfo GetProxyType( Justification = "interfaceType is annotated as preserve All members, so any Types returned from GetInterfaces should be preserved as well once https://github.com/mono/linker/issues/1731 is fixed.")] private GeneratedTypeInfo GenerateProxyType( [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicParameterlessConstructor)] Type baseType, - [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.All)] Type interfaceType) + [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.All)] Type interfaceType, + string interfaceParameter, string proxyParameter) { // Parameter validation is deferred until the point we need to create the proxy. // This prevents unnecessary overhead revalidating cached proxy types. @@ -174,27 +177,25 @@ private GeneratedTypeInfo GenerateProxyType( // The interface type must be an interface, not a class if (!interfaceType.IsInterface) { - // "T" is the generic parameter seen via the public contract - throw new ArgumentException(SR.Format(SR.InterfaceType_Must_Be_Interface, interfaceType.FullName), "T"); + throw new ArgumentException(SR.Format(SR.InterfaceType_Must_Be_Interface, interfaceType.FullName), interfaceParameter); } // The base type cannot be sealed because the proxy needs to subclass it. if (baseType.IsSealed) { - // "TProxy" is the generic parameter seen via the public contract - throw new ArgumentException(SR.Format(SR.BaseType_Cannot_Be_Sealed, baseType.FullName), "TProxy"); + throw new ArgumentException(SR.Format(SR.BaseType_Cannot_Be_Sealed, baseType.FullName), proxyParameter); } // The base type cannot be abstract if (baseType.IsAbstract) { - throw new ArgumentException(SR.Format(SR.BaseType_Cannot_Be_Abstract, baseType.FullName), "TProxy"); + throw new ArgumentException(SR.Format(SR.BaseType_Cannot_Be_Abstract, baseType.FullName), proxyParameter); } // The base type must have a public default ctor if (baseType.GetConstructor(Type.EmptyTypes) == null) { - throw new ArgumentException(SR.Format(SR.BaseType_Must_Have_Default_Ctor, baseType.FullName), "TProxy"); + throw new ArgumentException(SR.Format(SR.BaseType_Must_Have_Default_Ctor, baseType.FullName), proxyParameter); } // Create a type that derives from 'baseType' provided by caller diff --git a/src/libraries/System.Reflection.DispatchProxy/tests/DispatchProxyTests.cs b/src/libraries/System.Reflection.DispatchProxy/tests/DispatchProxyTests.cs index 3be55f8c0912c..fd0545c964866 100644 --- a/src/libraries/System.Reflection.DispatchProxy/tests/DispatchProxyTests.cs +++ b/src/libraries/System.Reflection.DispatchProxy/tests/DispatchProxyTests.cs @@ -15,19 +15,23 @@ namespace DispatchProxyTests { public static class DispatchProxyTests { - [Fact] - public static void Create_Proxy_Derives_From_DispatchProxy_BaseType() + [Theory] + [InlineData(false)] + [InlineData(true)] + public static void Create_Proxy_Derives_From_DispatchProxy_BaseType(bool useGenericCreate) { - TestType_IHelloService proxy = DispatchProxy.Create(); + TestType_IHelloService proxy = CreateHelper(useGenericCreate); Assert.NotNull(proxy); Assert.IsAssignableFrom(proxy); } - [Fact] - public static void Create_Proxy_Implements_All_Interfaces() + [Theory] + [InlineData(false)] + [InlineData(true)] + public static void Create_Proxy_Implements_All_Interfaces(bool useGenericCreate) { - TestType_IHelloAndGoodbyeService proxy = DispatchProxy.Create(); + TestType_IHelloAndGoodbyeService proxy = CreateHelper(useGenericCreate); Assert.NotNull(proxy); Type[] implementedInterfaces = typeof(TestType_IHelloAndGoodbyeService).GetTypeInfo().ImplementedInterfaces.ToArray(); @@ -37,17 +41,21 @@ public static void Create_Proxy_Implements_All_Interfaces() } } - [Fact] - public static void Create_Proxy_Internal_Interface() + [Theory] + [InlineData(false)] + [InlineData(true)] + public static void Create_Proxy_Internal_Interface(bool useGenericCreate) { - TestType_InternalInterfaceService proxy = DispatchProxy.Create(); + TestType_InternalInterfaceService proxy = CreateHelper(useGenericCreate); Assert.NotNull(proxy); } - [Fact] - public static void Create_Proxy_Implements_Internal_Interfaces() + [Theory] + [InlineData(false)] + [InlineData(true)] + public static void Create_Proxy_Implements_Internal_Interfaces(bool useGenericCreate) { - TestType_InternalInterfaceService proxy = DispatchProxy.Create(); + TestType_InternalInterfaceService proxy = CreateHelper(useGenericCreate); Assert.NotNull(proxy); // ensure we emit a valid attribute definition @@ -66,22 +74,26 @@ public static void Create_Proxy_Implements_Internal_Interfaces() Assert.Equal(name, actualName); } - [Fact] - public static void Create_Same_Proxy_Type_And_Base_Type_Reuses_Same_Generated_Type() + [Theory] + [InlineData(false)] + [InlineData(true)] + public static void Create_Same_Proxy_Type_And_Base_Type_Reuses_Same_Generated_Type(bool useGenericCreate) { - TestType_IHelloService proxy1 = DispatchProxy.Create(); - TestType_IHelloService proxy2 = DispatchProxy.Create(); + TestType_IHelloService proxy1 = CreateHelper(useGenericCreate); + TestType_IHelloService proxy2 = CreateHelper(useGenericCreate); Assert.NotNull(proxy1); Assert.NotNull(proxy2); Assert.IsType(proxy1.GetType(), proxy2); } - [Fact] - public static void Create_Proxy_Instances_Of_Same_Proxy_And_Base_Type_Are_Unique() + [Theory] + [InlineData(false)] + [InlineData(true)] + public static void Create_Proxy_Instances_Of_Same_Proxy_And_Base_Type_Are_Unique(bool useGenericCreate) { - TestType_IHelloService proxy1 = DispatchProxy.Create(); - TestType_IHelloService proxy2 = DispatchProxy.Create(); + TestType_IHelloService proxy1 = CreateHelper(useGenericCreate); + TestType_IHelloService proxy2 = CreateHelper(useGenericCreate); Assert.NotNull(proxy1); Assert.NotNull(proxy2); @@ -90,11 +102,13 @@ public static void Create_Proxy_Instances_Of_Same_Proxy_And_Base_Type_Are_Unique } - [Fact] - public static void Create_Same_Proxy_Type_With_Different_BaseType_Uses_Different_Generated_Type() + [Theory] + [InlineData(false)] + [InlineData(true)] + public static void Create_Same_Proxy_Type_With_Different_BaseType_Uses_Different_Generated_Type(bool useGenericCreate) { - TestType_IHelloService proxy1 = DispatchProxy.Create(); - TestType_IHelloService proxy2 = DispatchProxy.Create(); + TestType_IHelloService proxy1 = CreateHelper(useGenericCreate); + TestType_IHelloService proxy2 = CreateHelper(useGenericCreate); Assert.NotNull(proxy1); Assert.NotNull(proxy2); @@ -102,11 +116,13 @@ public static void Create_Same_Proxy_Type_With_Different_BaseType_Uses_Different string.Format("Proxy generated for base type {0} used same for base type {1}", typeof(TestDispatchProxy).Name, typeof(TestDispatchProxy).Name)); } - [Fact] - public static void Created_Proxy_With_Different_Proxy_Type_Use_Different_Generated_Type() + [Theory] + [InlineData(false)] + [InlineData(true)] + public static void Created_Proxy_With_Different_Proxy_Type_Use_Different_Generated_Type(bool useGenericCreate) { - TestType_IHelloService proxy1 = DispatchProxy.Create(); - TestType_IGoodbyeService proxy2 = DispatchProxy.Create(); + TestType_IHelloService proxy1 = CreateHelper(useGenericCreate); + TestType_IGoodbyeService proxy2 = CreateHelper(useGenericCreate); Assert.NotNull(proxy1); Assert.NotNull(proxy2); @@ -114,28 +130,68 @@ public static void Created_Proxy_With_Different_Proxy_Type_Use_Different_Generat string.Format("Proxy generated for type {0} used same for type {1}", typeof(TestType_IHelloService).Name, typeof(TestType_IGoodbyeService).Name)); } + [Theory] + [InlineData(false)] + [InlineData(true)] + public static void Create_Using_Concrete_Proxy_Type_Throws_ArgumentException(bool useGenericCreate) + { + AssertExtensions.Throws(useGenericCreate ? "T" : "interfaceType", () => CreateHelper(useGenericCreate)); + } + + [Theory] + [InlineData(false)] + [InlineData(true)] + public static void Create_Using_Sealed_BaseType_Throws_ArgumentException(bool useGenericCreate) + { + AssertExtensions.Throws(useGenericCreate ? "TProxy" : "proxyType", () => CreateHelper(useGenericCreate)); + } + + [Theory] + [InlineData(false)] + [InlineData(true)] + public static void Create_Using_Abstract_BaseType_Throws_ArgumentException(bool useGenericCreate) + { + AssertExtensions.Throws(useGenericCreate ? "TProxy" : "proxyType", () => CreateHelper(useGenericCreate)); + } + + [Theory] + [InlineData(false)] + [InlineData(true)] + public static void Create_Using_Abstract_Generic_BaseType_Throws_ArgumentException(bool useGenericCreate) + { + AssertExtensions.Throws(useGenericCreate ? "TProxy" : "proxyType", () => CreateHelper>(useGenericCreate)); + } + [Fact] - public static void Create_Using_Concrete_Proxy_Type_Throws_ArgumentException() + public static void Create_Using__Generic_BaseType_Throws_ArgumentException() { - AssertExtensions.Throws("T", () => DispatchProxy.Create()); + AssertExtensions.Throws("proxyType", () => DispatchProxy.Create(typeof(TestType_IHelloService), typeof(TestType_DipatchProxyGenericConstraint))); + } + + [Theory] + [InlineData(false)] + [InlineData(true)] + public static void Create_Using_BaseType_Without_Default_Ctor_Throws_ArgumentException(bool useGenericCreate) + { + AssertExtensions.Throws(useGenericCreate ? "TProxy" : "proxyType", () => CreateHelper(useGenericCreate)); } [Fact] - public static void Create_Using_Sealed_BaseType_Throws_ArgumentException() + public static void Non_Generic_Create_With_Null_InterfaceType_Throws_ArgumentNullException() { - AssertExtensions.Throws("TProxy", () => DispatchProxy.Create()); + AssertExtensions.Throws("interfaceType", () => DispatchProxy.Create(null, typeof(NoDefaultCtor_TestDispatchProxy))); } [Fact] - public static void Create_Using_Abstract_BaseType_Throws_ArgumentException() + public static void Non_Generic_Create_With_Null_ProxyType_Throws_ArgumentNullException() { - AssertExtensions.Throws("TProxy", () => DispatchProxy.Create()); + AssertExtensions.Throws("proxyType", () => DispatchProxy.Create(typeof(TestType_IHelloService), null)); } [Fact] - public static void Create_Using_BaseType_Without_Default_Ctor_Throws_ArgumentException() + public static void Non_Generic_Create_With_ProxyType_That_Is_Not_Assignable_To_DispatchProxy_Throws_ArgumentException() { - AssertExtensions.Throws("TProxy", () => DispatchProxy.Create()); + AssertExtensions.Throws("proxyType", () => DispatchProxy.Create(typeof(TestType_IHelloService), typeof(object))); } [Fact] @@ -156,44 +212,58 @@ public static void Create_Using_PrivateProxyAndInternalServiceWithExternalGeneri Assert.NotNull(TestType_PrivateProxy.Proxy()); } - [Fact] - public static void Create_Using_InternalProxy() + [Theory] + [InlineData(false)] + [InlineData(true)] + public static void Create_Using_InternalProxy(bool useGenericCreate) { - Assert.NotNull(DispatchProxy.Create()); + Assert.NotNull(CreateHelper(useGenericCreate)); } - [Fact] - public static void Create_Using_ExternalNonPublicService() + [Theory] + [InlineData(false)] + [InlineData(true)] + public static void Create_Using_ExternalNonPublicService(bool useGenericCreate) { - Assert.NotNull(DispatchProxy.Create()); + Assert.NotNull(CreateHelper(useGenericCreate)); } - [Fact] - public static void Create_Using_InternalProxyWithExternalNonPublicBaseType() + [Theory] + [InlineData(false)] + [InlineData(true)] + public static void Create_Using_InternalProxyWithExternalNonPublicBaseType(bool useGenericCreate) { - Assert.NotNull(DispatchProxy.Create()); + Assert.NotNull(CreateHelper(useGenericCreate)); } - [Fact] - public static void Create_Using_InternalServiceImplementingNonPublicExternalService() + [Theory] + [InlineData(false)] + [InlineData(true)] + public static void Create_Using_InternalServiceImplementingNonPublicExternalService(bool useGenericCreate) { - Assert.NotNull(DispatchProxy.Create()); + Assert.NotNull(CreateHelper(useGenericCreate)); } - [Fact] - public static void Create_Using_InternalServiceWithGenericArgumentBeingNonPublicExternalService() + [Theory] + [InlineData(false)] + [InlineData(true)] + public static void Create_Using_InternalServiceWithGenericArgumentBeingNonPublicExternalService(bool useGenericCreate) { - Assert.NotNull(DispatchProxy.Create()); + Assert.NotNull(CreateHelper(useGenericCreate)); } - [Fact] - public static void Create_Using_InternalProxyWithBaseTypeImplementingServiceWithgenericArgumentBeingNonPublicExternalService() + [Theory] + [InlineData(false)] + [InlineData(true)] + public static void Create_Using_InternalProxyWithBaseTypeImplementingServiceWithgenericArgumentBeingNonPublicExternalService(bool useGenericCreate) { - Assert.NotNull(DispatchProxy.Create()); + Assert.NotNull(CreateHelper(useGenericCreate)); } - [Fact] - public static void Invoke_Receives_Correct_MethodInfo_And_Arguments() + [Theory] + [InlineData(false)] + [InlineData(true)] + public static void Invoke_Receives_Correct_MethodInfo_And_Arguments(bool useGenericCreate) { bool wasInvoked = false; StringBuilder errorBuilder = new StringBuilder(); @@ -225,7 +295,7 @@ public static void Invoke_Receives_Correct_MethodInfo_And_Arguments() return "success"; }; - TestType_IHelloService proxy = DispatchProxy.Create(); + TestType_IHelloService proxy = CreateHelper(useGenericCreate); Assert.NotNull(proxy); TestDispatchProxy dispatchProxy = proxy as TestDispatchProxy; @@ -241,12 +311,14 @@ public static void Invoke_Receives_Correct_MethodInfo_And_Arguments() Assert.True(errorBuilder.Length == 0, errorBuilder.ToString()); } - [Fact] - public static void Invoke_Receives_Correct_MethodInfo() + [Theory] + [InlineData(false)] + [InlineData(true)] + public static void Invoke_Receives_Correct_MethodInfo(bool useGenericCreate) { MethodInfo invokedMethod = null; - TestType_IHelloService proxy = DispatchProxy.Create(); + TestType_IHelloService proxy = CreateHelper(useGenericCreate); ((TestDispatchProxy)proxy).CallOnInvoke = (method, args) => { invokedMethod = method; @@ -259,12 +331,14 @@ public static void Invoke_Receives_Correct_MethodInfo() Assert.True(invokedMethod != null && expectedMethod == invokedMethod, string.Format("Invoke expected method {0} but actual was {1}", expectedMethod, invokedMethod)); } - [Fact] - public static void Invoke_Receives_Correct_Arguments() + [Theory] + [InlineData(false)] + [InlineData(true)] + public static void Invoke_Receives_Correct_Arguments(bool useGenericCreate) { object[] actualArgs = null; - TestType_IHelloService proxy = DispatchProxy.Create(); + TestType_IHelloService proxy = CreateHelper(useGenericCreate); ((TestDispatchProxy)proxy).CallOnInvoke = (method, args) => { actualArgs = args; @@ -285,10 +359,12 @@ public static void Invoke_Receives_Correct_Arguments() } } - [Fact] - public static void Invoke_Returns_Correct_Value() + [Theory] + [InlineData(false)] + [InlineData(true)] + public static void Invoke_Returns_Correct_Value(bool useGenericCreate) { - TestType_IHelloService proxy = DispatchProxy.Create(); + TestType_IHelloService proxy = CreateHelper(useGenericCreate); ((TestDispatchProxy)proxy).CallOnInvoke = (method, args) => { return "testReturn"; @@ -299,13 +375,15 @@ public static void Invoke_Returns_Correct_Value() Assert.Equal(expectedResult, actualResult); } - [Fact] - public static void Invoke_Multiple_Parameters_Receives_Correct_Arguments() + [Theory] + [InlineData(false)] + [InlineData(true)] + public static void Invoke_Multiple_Parameters_Receives_Correct_Arguments(bool useGenericCreate) { object[] invokedArgs = null; object[] expectedArgs = new object[] { (int)42, "testString", (double)5.0 }; - TestType_IMultipleParameterService proxy = DispatchProxy.Create(); + TestType_IMultipleParameterService proxy = CreateHelper(useGenericCreate); ((TestDispatchProxy)proxy).CallOnInvoke = (method, args) => { invokedArgs = args; @@ -326,14 +404,16 @@ public static void Invoke_Multiple_Parameters_Receives_Correct_Arguments() } } - [Fact] - public static void Invoke_Multiple_Parameters_Via_Params_Receives_Correct_Arguments() + [Theory] + [InlineData(false)] + [InlineData(true)] + public static void Invoke_Multiple_Parameters_Via_Params_Receives_Correct_Arguments(bool useGenericCreate) { object[] actualArgs = null; object[] invokedArgs = null; object[] expectedArgs = new object[] { 42, "testString", 5.0 }; - TestType_IMultipleParameterService proxy = DispatchProxy.Create(); + TestType_IMultipleParameterService proxy = CreateHelper(useGenericCreate); ((TestDispatchProxy)proxy).CallOnInvoke = (method, args) => { invokedArgs = args; @@ -360,12 +440,14 @@ public static void Invoke_Multiple_Parameters_Via_Params_Receives_Correct_Argume } } - [Fact] - public static void Invoke_Void_Returning_Method_Accepts_Null_Return() + [Theory] + [InlineData(false)] + [InlineData(true)] + public static void Invoke_Void_Returning_Method_Accepts_Null_Return(bool useGenericCreate) { MethodInfo invokedMethod = null; - TestType_IOneWay proxy = DispatchProxy.Create(); + TestType_IOneWay proxy = CreateHelper(useGenericCreate); ((TestDispatchProxy)proxy).CallOnInvoke = (method, args) => { invokedMethod = method; @@ -378,12 +460,14 @@ public static void Invoke_Void_Returning_Method_Accepts_Null_Return() Assert.True(invokedMethod != null && expectedMethod == invokedMethod, string.Format("Invoke expected method {0} but actual was {1}", expectedMethod, invokedMethod)); } - [Fact] - public static void Invoke_Same_Method_Multiple_Interfaces_Calls_Correct_Method() + [Theory] + [InlineData(false)] + [InlineData(true)] + public static void Invoke_Same_Method_Multiple_Interfaces_Calls_Correct_Method(bool useGenericCreate) { List invokedMethods = new List(); - TestType_IHelloService1And2 proxy = DispatchProxy.Create(); + TestType_IHelloService1And2 proxy = CreateHelper(useGenericCreate); ((TestDispatchProxy)proxy).CallOnInvoke = (method, args) => { invokedMethods.Add(method); @@ -402,13 +486,15 @@ public static void Invoke_Same_Method_Multiple_Interfaces_Calls_Correct_Method() Assert.True(invokedMethods[1] != null && expectedMethod == invokedMethods[1], string.Format("Second invoke should have been TestType_IHelloService2.Hello but actual was {0}", invokedMethods[1])); } - [Fact] - public static void Invoke_Thrown_Exception_Rethrown_To_Caller() + [Theory] + [InlineData(false)] + [InlineData(true)] + public static void Invoke_Thrown_Exception_Rethrown_To_Caller(bool useGenericCreate) { Exception actualException = null; InvalidOperationException expectedException = new InvalidOperationException("testException"); - TestType_IHelloService proxy = DispatchProxy.Create(); + TestType_IHelloService proxy = CreateHelper(useGenericCreate); ((TestDispatchProxy)proxy).CallOnInvoke = (method, args) => { throw expectedException; @@ -426,12 +512,14 @@ public static void Invoke_Thrown_Exception_Rethrown_To_Caller() Assert.Equal(expectedException, actualException); } - [Fact] - public static void Invoke_Property_Setter_And_Getter_Invokes_Correct_Methods() + [Theory] + [InlineData(false)] + [InlineData(true)] + public static void Invoke_Property_Setter_And_Getter_Invokes_Correct_Methods(bool useGenericCreate) { List invokedMethods = new List(); - TestType_IPropertyService proxy = DispatchProxy.Create(); + TestType_IPropertyService proxy = CreateHelper(useGenericCreate); ((TestDispatchProxy)proxy).CallOnInvoke = (method, args) => { invokedMethods.Add(method); @@ -459,33 +547,52 @@ public static void Invoke_Property_Setter_And_Getter_Invokes_Correct_Methods() } - [Fact] - public static void Proxy_Declares_Interface_Properties() + [Theory] + [InlineData(false)] + [InlineData(true)] + public static void Proxy_Declares_Interface_Properties(bool useGenericCreate) { - TestType_IPropertyService proxy = DispatchProxy.Create(); + TestType_IPropertyService proxy = CreateHelper(useGenericCreate); PropertyInfo propertyInfo = proxy.GetType().GetTypeInfo().GetDeclaredProperty("ReadWrite"); Assert.NotNull(propertyInfo); } #if NETCOREAPP [Fact] - public static void Invoke_Event_Add_And_Remove_And_Raise_Invokes_Correct_Methods() + public static void Invoke_Event_Add_And_Remove_And_Raise_Invokes_Correct_Methods_Generic_And_Non_Generic_Tests() { // C# cannot emit raise_Xxx method for the event, so we must use System.Reflection.Emit to generate such event. AssemblyBuilder ab = AssemblyBuilder.DefineDynamicAssembly(new AssemblyName("EventBuilder"), AssemblyBuilderAccess.Run); ModuleBuilder modb = ab.DefineDynamicModule("mod"); - TypeBuilder tb = modb.DefineType("TestType_IEventService", TypeAttributes.Public | TypeAttributes.Interface | TypeAttributes.Abstract); + TypeBuilder tb = modb.DefineType($"TestType_IEventService", TypeAttributes.Public | TypeAttributes.Interface | TypeAttributes.Abstract); EventBuilder eb = tb.DefineEvent("AddRemoveRaise", EventAttributes.None, typeof(EventHandler)); eb.SetAddOnMethod(tb.DefineMethod("add_AddRemoveRaise", MethodAttributes.Public | MethodAttributes.Abstract | MethodAttributes.Virtual, typeof(void), new Type[] { typeof(EventHandler) })); eb.SetRemoveOnMethod(tb.DefineMethod("remove_AddRemoveRaise", MethodAttributes.Public | MethodAttributes.Abstract | MethodAttributes.Virtual, typeof(void), new Type[] { typeof(EventHandler) })); eb.SetRaiseMethod(tb.DefineMethod("raise_AddRemoveRaise", MethodAttributes.Public | MethodAttributes.Abstract | MethodAttributes.Virtual, typeof(void), new Type[] { typeof(EventArgs) })); TypeInfo ieventServiceTypeInfo = tb.CreateTypeInfo(); + Invoke_Event_Add_And_Remove_And_Raise_Invokes_Correct_Methods(ieventServiceTypeInfo, true); + Invoke_Event_Add_And_Remove_And_Raise_Invokes_Correct_Methods(ieventServiceTypeInfo, false); + } + + static void Invoke_Event_Add_And_Remove_And_Raise_Invokes_Correct_Methods(TypeInfo ieventServiceTypeInfo, bool useGenericCreate) + { List invokedMethods = new List(); - object proxy = - typeof(DispatchProxy) - .GetRuntimeMethod("Create", Type.EmptyTypes).MakeGenericMethod(ieventServiceTypeInfo.AsType(), typeof(TestDispatchProxy)) - .Invoke(null, null); + object proxy; + if (useGenericCreate) + { + proxy = + typeof(DispatchProxy) + .GetRuntimeMethod("Create", Type.EmptyTypes).MakeGenericMethod(ieventServiceTypeInfo.AsType(), typeof(TestDispatchProxy)) + .Invoke(null, null); + } + else + { + proxy = typeof(DispatchProxy) + .GetRuntimeMethod("Create", new Type[] { typeof(Type), typeof(Type) })! + .Invoke(null, new object[] { ieventServiceTypeInfo.AsType(), typeof(TestDispatchProxy) }); + } + ((TestDispatchProxy)proxy).CallOnInvoke = (method, args) => { invokedMethods.Add(method); @@ -517,28 +624,30 @@ public static void Invoke_Event_Add_And_Remove_And_Raise_Invokes_Correct_Methods } #endif - [Fact] - public static void Proxy_Declares_Interface_Events() + [Theory] + [InlineData(false)] + [InlineData(true)] + public static void Proxy_Declares_Interface_Events(bool useGenericCreate) { - TestType_IEventService proxy = DispatchProxy.Create(); + TestType_IEventService proxy = CreateHelper(useGenericCreate); EventInfo eventInfo = proxy.GetType().GetTypeInfo().GetDeclaredEvent("AddRemove"); Assert.NotNull(eventInfo); } - - [Fact] - public static void Invoke_Indexer_Setter_And_Getter_Invokes_Correct_Methods() + [Theory] + [InlineData(false)] + [InlineData(true)] + public static void Invoke_Indexer_Setter_And_Getter_Invokes_Correct_Methods(bool useGenericCreate) { List invokedMethods = new List(); - TestType_IIndexerService proxy = DispatchProxy.Create(); + TestType_IIndexerService proxy = CreateHelper(useGenericCreate); ((TestDispatchProxy)proxy).CallOnInvoke = (method, args) => { invokedMethods.Add(method); return null; }; - proxy["key"] = "testValue"; string actualValue = proxy["key"]; @@ -558,17 +667,19 @@ public static void Invoke_Indexer_Setter_And_Getter_Invokes_Correct_Methods() Assert.Null(actualValue); } - [Fact] - public static void Proxy_Declares_Interface_Indexers() + [Theory] + [InlineData(false)] + [InlineData(true)] + public static void Proxy_Declares_Interface_Indexers(bool useGenericCreate) { - TestType_IIndexerService proxy = DispatchProxy.Create(); + TestType_IIndexerService proxy = CreateHelper(useGenericCreate); PropertyInfo propertyInfo = proxy.GetType().GetTypeInfo().GetDeclaredProperty("Item"); Assert.NotNull(propertyInfo); } - static void testGenericMethodRoundTrip(T testValue) + static void TestGenericMethodRoundTrip(T testValue, bool useGenericCreate) { - var proxy = DispatchProxy.Create(); + var proxy = CreateHelper(useGenericCreate); ((TestDispatchProxy)proxy).CallOnInvoke = (mi, a) => { Assert.True(mi.IsGenericMethod); @@ -581,36 +692,40 @@ static void testGenericMethodRoundTrip(T testValue) Assert.Equal(proxy.Echo(testValue), testValue); } - [Fact] - public static void Invoke_Generic_Method() + [Theory] + [InlineData(false)] + [InlineData(true)] + public static void Invoke_Generic_Method(bool useGenericCreate) { //string - testGenericMethodRoundTrip("asdf"); + TestGenericMethodRoundTrip("asdf", useGenericCreate); //reference type - testGenericMethodRoundTrip(new Version(1, 0, 0, 0)); + TestGenericMethodRoundTrip(new Version(1, 0, 0, 0), useGenericCreate); //value type - testGenericMethodRoundTrip(42); + TestGenericMethodRoundTrip(42, useGenericCreate); //enum type - testGenericMethodRoundTrip(DayOfWeek.Monday); + TestGenericMethodRoundTrip(DayOfWeek.Monday, useGenericCreate); } - [Fact] - public static void Invoke_Ref_Out_In_Method() + [Theory] + [InlineData(false)] + [InlineData(true)] + public static void Invoke_Ref_Out_In_Method(bool useGenericCreate) { string value = "Hello"; - testRefOutInInvocation(p => p.InAttribute(value), "Hello"); - testRefOutInInvocation(p => p.InAttribute_OutAttribute(value), "Hello"); - testRefOutInInvocation(p => p.InAttribute_Ref(ref value), "Hello"); - testRefOutInInvocation(p => p.Out(out _), null); - testRefOutInInvocation(p => p.OutAttribute(value), "Hello"); - testRefOutInInvocation(p => p.Ref(ref value), "Hello"); - testRefOutInInvocation(p => p.In(in value), "Hello"); + TestRefOutInInvocation(p => p.InAttribute(value), "Hello", useGenericCreate); + TestRefOutInInvocation(p => p.InAttribute_OutAttribute(value), "Hello", useGenericCreate); + TestRefOutInInvocation(p => p.InAttribute_Ref(ref value), "Hello", useGenericCreate); + TestRefOutInInvocation(p => p.Out(out _), null, useGenericCreate); + TestRefOutInInvocation(p => p.OutAttribute(value), "Hello", useGenericCreate); + TestRefOutInInvocation(p => p.Ref(ref value), "Hello", useGenericCreate); + TestRefOutInInvocation(p => p.In(in value), "Hello", useGenericCreate); } - private static void testRefOutInInvocation(Action invocation, string expected) + private static void TestRefOutInInvocation(Action invocation, string expected, bool useGenericCreate) { - var proxy = DispatchProxy.Create(); + var proxy = CreateHelper(useGenericCreate); string result = "Failed"; @@ -625,17 +740,19 @@ private static void testRefOutInInvocation(Action invocation, Assert.Equal(expected, result); } - private static TestType_IHelloService CreateTestHelloProxy() => - DispatchProxy.Create(); + private static TestType_IHelloService CreateTestHelloProxy(bool useGenericCreate) => + CreateHelper(useGenericCreate); - [ActiveIssue("https://github.com/dotnet/runtime/issues/62503", TestRuntimes.Mono)] - [Fact] - public static void Test_Unloadability() + [ActiveIssue("https://github.com/dotnet/runtime/issues/62503", TestRuntimes.Mono)] + [Theory] + [InlineData(false)] + [InlineData(true)] + public static void Test_Unloadability(bool useGenericCreate) { if (typeof(DispatchProxyTests).Assembly.Location == "") return; - WeakReference wr = CreateProxyInUnloadableAlc(); + WeakReference wr = CreateProxyInUnloadableAlc(useGenericCreate); for (int i = 0; i < 10 && wr.IsAlive; i++) { @@ -646,24 +763,26 @@ public static void Test_Unloadability() Assert.False(wr.IsAlive, "The ALC could not be unloaded."); [MethodImpl(MethodImplOptions.NoInlining)] - static WeakReference CreateProxyInUnloadableAlc() + static WeakReference CreateProxyInUnloadableAlc(bool useGenericCreate) { var alc = new AssemblyLoadContext(nameof(Test_Unloadability), true); alc.LoadFromAssemblyPath(typeof(DispatchProxyTests).Assembly.Location) .GetType(typeof(DispatchProxyTests).FullName, true) .GetMethod(nameof(CreateTestHelloProxy), BindingFlags.Static | BindingFlags.NonPublic) - .Invoke(null, null); + .Invoke(null, new object[] { useGenericCreate }); return new WeakReference(alc); } } - [Fact] - public static void Test_Multiple_AssemblyLoadContexts() + [Theory] + [InlineData(false)] + [InlineData(true)] + public static void Test_Multiple_AssemblyLoadContexts(bool useGenericCreate) { if (typeof(DispatchProxyTests).Assembly.Location == "") return; - object proxyDefaultAlc = CreateTestDispatchProxy(typeof(TestDispatchProxy)); + object proxyDefaultAlc = CreateTestDispatchProxy(typeof(TestDispatchProxy), useGenericCreate); Assert.True(proxyDefaultAlc.GetType().IsAssignableTo(typeof(TestDispatchProxy))); Type proxyCustomAlcType = @@ -671,15 +790,35 @@ public static void Test_Multiple_AssemblyLoadContexts() .LoadFromAssemblyPath(typeof(DispatchProxyTests).Assembly.Location) .GetType(typeof(TestDispatchProxy).FullName, true); - object proxyCustomAlc = CreateTestDispatchProxy(proxyCustomAlcType); + object proxyCustomAlc = CreateTestDispatchProxy(proxyCustomAlcType, useGenericCreate); Assert.True(proxyCustomAlc.GetType().IsAssignableTo(proxyCustomAlcType)); - static object CreateTestDispatchProxy(Type type) => - typeof(DispatchProxy) - .GetMethod(nameof(DispatchProxy.Create)) - // It has to be a type shared in both ALCs. - .MakeGenericMethod(typeof(IDisposable), type) - .Invoke(null, null); + static object CreateTestDispatchProxy(Type type, bool useGenericCreate) + { + if (useGenericCreate) + { + return typeof(DispatchProxy) + // It has to be a type shared in both ALCs. + .GetMethod("Create", Type.EmptyTypes).MakeGenericMethod(typeof(IDisposable), type) + .Invoke(null, null); + } + else + { + return typeof(DispatchProxy) + .GetMethod("Create", new Type[] { typeof(Type), typeof(Type) })! + .Invoke(null, new object[] { typeof(IDisposable), type }); + } + } + } + + private static TInterface CreateHelper(bool useGenericCreate) where TProxy : DispatchProxy + { + if (useGenericCreate) + { + return DispatchProxy.Create(); + } + + return (TInterface)DispatchProxy.Create(typeof(TInterface), typeof(TProxy)); } } } diff --git a/src/libraries/System.Reflection.DispatchProxy/tests/TestTypes.cs b/src/libraries/System.Reflection.DispatchProxy/tests/TestTypes.cs index dca9f2d266c8d..16637c9a67d65 100644 --- a/src/libraries/System.Reflection.DispatchProxy/tests/TestTypes.cs +++ b/src/libraries/System.Reflection.DispatchProxy/tests/TestTypes.cs @@ -118,6 +118,8 @@ public class TestType_ConcreteClass public string Echo(string s) { return null; } } +class TestType_DipatchProxyGenericConstraint where T : DispatchProxy { } + // Negative -- demonstrates base type that is sealed and should generate exception public sealed class Sealed_TestDispatchProxy : DispatchProxy { @@ -189,6 +191,8 @@ protected override object Invoke(MethodInfo targetMethod, object[] args) } } +abstract class Abstract_GenericDispatchProxy : DispatchProxy { } + // Negative -- demonstrates base type that has no public default ctor public class NoDefaultCtor_TestDispatchProxy : DispatchProxy {