Skip to content
This repository was archived by the owner on Jan 23, 2023. It is now read-only.

Commit e8f650d

Browse files
committed
Make DispatchProxy declares Property/EventInfos of the interface.
This commit adds code to declare properties and events to generating proxy type as well. Note * Because declaring properties and events requires accessor methods, this commit changes AddMethodImpl to return created MethodBuilder. * This declares EqualityComparer for MethodInfo to avoid reference based comparison. * The test case now uses System.Reflection.Emit to generate the event with raise_ method which is not supported in C#.
1 parent 6c36a16 commit e8f650d

4 files changed

Lines changed: 296 additions & 4 deletions

File tree

‎src/System.Reflection.DispatchProxy/src/System/Reflection/DispatchProxyGenerator.cs‎

Lines changed: 174 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -426,13 +426,78 @@ internal void AddInterfaceImpl(Type iface)
426426
_assembly.EnsureTypeIsVisible(iface);
427427

428428
_tb.AddInterfaceImplementation(iface);
429+
430+
// AccessorMethods -> Metadata mappings.
431+
var propertyMap = new Dictionary<MethodInfo, PropertyAccessorInfo>(MethodInfoEqualityComparer.Instance);
432+
foreach (PropertyInfo pi in iface.GetRuntimeProperties())
433+
{
434+
var ai = new PropertyAccessorInfo(pi.GetMethod, pi.SetMethod);
435+
if (pi.GetMethod != null)
436+
propertyMap[pi.GetMethod] = ai;
437+
if (pi.SetMethod != null)
438+
propertyMap[pi.SetMethod] = ai;
439+
}
440+
441+
var eventMap = new Dictionary<MethodInfo, EventAccessorInfo>(MethodInfoEqualityComparer.Instance);
442+
foreach (EventInfo ei in iface.GetRuntimeEvents())
443+
{
444+
var ai = new EventAccessorInfo(ei.AddMethod, ei.RemoveMethod, ei.RaiseMethod);
445+
if (ei.AddMethod != null)
446+
eventMap[ei.AddMethod] = ai;
447+
if (ei.RemoveMethod != null)
448+
eventMap[ei.RemoveMethod] = ai;
449+
if (ei.RaiseMethod != null)
450+
eventMap[ei.RaiseMethod] = ai;
451+
}
452+
429453
foreach (MethodInfo mi in iface.GetRuntimeMethods())
430454
{
431-
AddMethodImpl(mi);
455+
MethodBuilder mdb = AddMethodImpl(mi);
456+
PropertyAccessorInfo associatedProperty;
457+
if (propertyMap.TryGetValue(mi, out associatedProperty))
458+
{
459+
if (MethodInfoEqualityComparer.Instance.Equals(associatedProperty.InterfaceGetMethod, mi))
460+
associatedProperty.GetMethodBuilder = mdb;
461+
else
462+
associatedProperty.SetMethodBuilder = mdb;
463+
}
464+
465+
EventAccessorInfo associatedEvent;
466+
if (eventMap.TryGetValue(mi, out associatedEvent))
467+
{
468+
if (MethodInfoEqualityComparer.Instance.Equals(associatedEvent.InterfaceAddMethod, mi))
469+
associatedEvent.AddMethodBuilder = mdb;
470+
else if (MethodInfoEqualityComparer.Instance.Equals(associatedEvent.InterfaceRemoveMethod, mi))
471+
associatedEvent.RemoveMethodBuilder = mdb;
472+
else
473+
associatedEvent.RaiseMethodBuilder = mdb;
474+
}
475+
}
476+
477+
foreach (PropertyInfo pi in iface.GetRuntimeProperties())
478+
{
479+
PropertyAccessorInfo ai = propertyMap[pi.GetMethod ?? pi.SetMethod];
480+
PropertyBuilder pb = _tb.DefineProperty(pi.Name, pi.Attributes, pi.PropertyType, pi.GetIndexParameters().Select(p => p.ParameterType).ToArray());
481+
if (ai.GetMethodBuilder != null)
482+
pb.SetGetMethod(ai.GetMethodBuilder);
483+
if (ai.SetMethodBuilder != null)
484+
pb.SetSetMethod(ai.SetMethodBuilder);
485+
}
486+
487+
foreach (EventInfo ei in iface.GetRuntimeEvents())
488+
{
489+
EventAccessorInfo ai = eventMap[ei.AddMethod ?? ei.RemoveMethod];
490+
EventBuilder eb = _tb.DefineEvent(ei.Name, ei.Attributes, ei.EventHandlerType);
491+
if (ai.AddMethodBuilder != null)
492+
eb.SetAddOnMethod(ai.AddMethodBuilder);
493+
if (ai.RemoveMethodBuilder != null)
494+
eb.SetRemoveOnMethod(ai.RemoveMethodBuilder);
495+
if (ai.RaiseMethodBuilder != null)
496+
eb.SetRaiseMethod(ai.RaiseMethodBuilder);
432497
}
433498
}
434499

435-
private void AddMethodImpl(MethodInfo mi)
500+
private MethodBuilder AddMethodImpl(MethodInfo mi)
436501
{
437502
ParameterInfo[] parameters = mi.GetParameters();
438503
Type[] paramTypes = ParamTypes(parameters, false);
@@ -541,6 +606,7 @@ private void AddMethodImpl(MethodInfo mi)
541606
il.Emit(OpCodes.Ret);
542607

543608
_tb.DefineMethodOverride(mdb, mi);
609+
return mdb;
544610
}
545611

546612
private static Type[] ParamTypes(ParameterInfo[] parms, bool noByRef)
@@ -831,6 +897,112 @@ internal void EndSet(Type stackType)
831897
_il.Emit(OpCodes.Stelem_Ref);
832898
}
833899
}
900+
901+
private sealed class PropertyAccessorInfo
902+
{
903+
public MethodInfo InterfaceGetMethod { get; }
904+
public MethodInfo InterfaceSetMethod { get; }
905+
public MethodBuilder GetMethodBuilder { get; set; }
906+
public MethodBuilder SetMethodBuilder { get; set; }
907+
908+
public PropertyAccessorInfo(MethodInfo interfaceGetMethod, MethodInfo interfaceSetMethod)
909+
{
910+
InterfaceGetMethod = interfaceGetMethod;
911+
InterfaceSetMethod = interfaceSetMethod;
912+
}
913+
}
914+
915+
private sealed class EventAccessorInfo
916+
{
917+
public MethodInfo InterfaceAddMethod { get; }
918+
public MethodInfo InterfaceRemoveMethod { get; }
919+
public MethodInfo InterfaceRaiseMethod { get; }
920+
public MethodBuilder AddMethodBuilder { get; set; }
921+
public MethodBuilder RemoveMethodBuilder { get; set; }
922+
public MethodBuilder RaiseMethodBuilder { get; set; }
923+
924+
public EventAccessorInfo(MethodInfo interfaceAddMethod, MethodInfo interfaceRemoveMethod, MethodInfo interfaceRaiseMethod)
925+
{
926+
InterfaceAddMethod = interfaceAddMethod;
927+
InterfaceRemoveMethod = interfaceRemoveMethod;
928+
InterfaceRaiseMethod = interfaceRaiseMethod;
929+
}
930+
}
931+
932+
private sealed class MethodInfoEqualityComparer : EqualityComparer<MethodInfo>
933+
{
934+
public static readonly MethodInfoEqualityComparer Instance = new MethodInfoEqualityComparer();
935+
936+
private MethodInfoEqualityComparer() { }
937+
938+
public sealed override bool Equals(MethodInfo left, MethodInfo right)
939+
{
940+
if (ReferenceEquals(left, right))
941+
return true;
942+
943+
if (left == null)
944+
return right == null;
945+
else if (right == null)
946+
return false;
947+
948+
// This assembly should work in netstandard1.3,
949+
// so we cannot use MemberInfo.MetadataToken here.
950+
// Therefore, it compares honestly referring ECMA-335 I.8.6.1.6 Signature Matching.
951+
if (!Equals(left.DeclaringType, right.DeclaringType))
952+
return false;
953+
954+
if (!Equals(left.ReturnType, right.ReturnType))
955+
return false;
956+
957+
if (left.CallingConvention != right.CallingConvention)
958+
return false;
959+
960+
if (left.IsStatic != right.IsStatic)
961+
return false;
962+
963+
if ( left.Name != right.Name)
964+
return false;
965+
966+
Type[] leftGenericParameters = left.GetGenericArguments();
967+
Type[] rightGenericParameters = right.GetGenericArguments();
968+
if (leftGenericParameters.Length != rightGenericParameters.Length)
969+
return false;
970+
971+
for (int i = 0; i < leftGenericParameters.Length; i++)
972+
{
973+
if (!Equals(leftGenericParameters[i], rightGenericParameters[i]))
974+
return false;
975+
}
976+
977+
ParameterInfo[] leftParameters = left.GetParameters();
978+
ParameterInfo[] rightParameters = right.GetParameters();
979+
if (leftParameters.Length != rightParameters.Length)
980+
return false;
981+
982+
for (int i = 0; i < leftParameters.Length; i++)
983+
{
984+
if (!Equals(leftParameters[i].ParameterType, rightParameters[i].ParameterType))
985+
return false;
986+
}
987+
988+
return true;
989+
}
990+
991+
public sealed override int GetHashCode(MethodInfo obj)
992+
{
993+
if (obj == null)
994+
return 0;
995+
996+
int hashCode = obj.DeclaringType.GetHashCode();
997+
hashCode ^= obj.Name.GetHashCode();
998+
foreach (ParameterInfo parameter in obj.GetParameters())
999+
{
1000+
hashCode ^= parameter.ParameterType.GetHashCode();
1001+
}
1002+
1003+
return hashCode;
1004+
}
1005+
}
8341006
}
8351007
}
8361008
}

‎src/System.Reflection.DispatchProxy/tests/DispatchProxyTests.cs‎

Lines changed: 108 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
using System.Collections.Generic;
77
using System.Linq;
88
using System.Reflection;
9+
using System.Reflection.Emit;
910
using System.Text;
1011
using Xunit;
1112

@@ -381,10 +382,116 @@ public static void Invoke_Property_Setter_And_Getter_Invokes_Correct_Methods()
381382
expectedMethod.Name, invokedMethods[0]));
382383

383384
expectedMethod = propertyInfo.GetMethod;
384-
Assert.True(invokedMethods[1] != null && expectedMethod == invokedMethods[1], String.Format("First invoke should have been {0} but actual was {1}",
385+
Assert.True(invokedMethods[1] != null && expectedMethod == invokedMethods[1], String.Format("Second invoke should have been {0} but actual was {1}",
385386
expectedMethod.Name, invokedMethods[1]));
386387

387388
Assert.Null(actualValue);
388389
}
390+
391+
392+
[Fact]
393+
public static void Proxy_Declares_Interface_Properties()
394+
{
395+
TestType_IPropertyService proxy = DispatchProxy.Create<TestType_IPropertyService, TestDispatchProxy>();
396+
PropertyInfo propertyInfo = proxy.GetType().GetTypeInfo().GetDeclaredProperty("ReadWrite");
397+
Assert.NotNull(propertyInfo);
398+
}
399+
400+
[Fact]
401+
public static void Invoke_Event_Add_And_Remove_And_Raise_Invokes_Correct_Methods()
402+
{
403+
// C# cannot emit raise_Xxx method for the event, so we must use System.Reflection.Emit to generate such event.
404+
AssemblyBuilder ab = AssemblyBuilder.DefineDynamicAssembly(new AssemblyName("EventBuilder"), AssemblyBuilderAccess.Run);
405+
ModuleBuilder modb = ab.DefineDynamicModule("mod");
406+
TypeBuilder tb = modb.DefineType("TestType_IEventService", TypeAttributes.Public | TypeAttributes.Interface | TypeAttributes.Abstract);
407+
EventBuilder eb = tb.DefineEvent("AddRemoveRaise", EventAttributes.None, typeof(EventHandler));
408+
eb.SetAddOnMethod(tb.DefineMethod("add_AddRemoveRaise", MethodAttributes.Public | MethodAttributes.Abstract | MethodAttributes.Virtual, typeof(void), new Type[] { typeof(EventHandler) }));
409+
eb.SetRemoveOnMethod( tb.DefineMethod("remove_AddRemoveRaise", MethodAttributes.Public | MethodAttributes.Abstract | MethodAttributes.Virtual, typeof(void), new Type[] { typeof(EventHandler) }));
410+
eb.SetRaiseMethod(tb.DefineMethod("raise_AddRemoveRaise", MethodAttributes.Public | MethodAttributes.Abstract | MethodAttributes.Virtual, typeof(void), new Type[] { typeof(EventArgs) }));
411+
TypeInfo ieventServiceTypeInfo = tb.CreateTypeInfo();
412+
413+
List<MethodInfo> invokedMethods = new List<MethodInfo>();
414+
object proxy =
415+
typeof(DispatchProxy)
416+
.GetRuntimeMethod("Create", Array.Empty<Type>()).MakeGenericMethod(ieventServiceTypeInfo.AsType(), typeof(TestDispatchProxy))
417+
.Invoke(null, null);
418+
((TestDispatchProxy)proxy).CallOnInvoke = (method, args) =>
419+
{
420+
invokedMethods.Add(method);
421+
return null;
422+
};
423+
424+
EventHandler handler = new EventHandler((sender, e) => {});
425+
426+
proxy.GetType().GetRuntimeMethods().Single(m => m.Name == "add_AddRemoveRaise").Invoke(proxy, new object[] { handler });
427+
proxy.GetType().GetRuntimeMethods().Single(m => m.Name == "raise_AddRemoveRaise").Invoke(proxy, new object[] { EventArgs.Empty });
428+
proxy.GetType().GetRuntimeMethods().Single(m => m.Name == "remove_AddRemoveRaise").Invoke(proxy, new object[] { handler });
429+
430+
Assert.True(invokedMethods.Count == 3, String.Format("Expected 3 method invocations but received {0}", invokedMethods.Count));
431+
432+
EventInfo eventInfo = ieventServiceTypeInfo.GetDeclaredEvent("AddRemoveRaise");
433+
Assert.NotNull(eventInfo);
434+
435+
MethodInfo expectedMethod = eventInfo.AddMethod;
436+
Assert.True(invokedMethods[0] != null && expectedMethod == invokedMethods[0], String.Format("First invoke should have been {0} but actual was {1}",
437+
expectedMethod.Name, invokedMethods[0]));
438+
439+
expectedMethod = eventInfo.RaiseMethod;
440+
Assert.True(invokedMethods[1] != null && expectedMethod == invokedMethods[1], String.Format("Second invoke should have been {0} but actual was {1}",
441+
expectedMethod.Name, invokedMethods[1]));
442+
443+
expectedMethod = eventInfo.RemoveMethod;
444+
Assert.True(invokedMethods[2] != null && expectedMethod == invokedMethods[2], String.Format("Third invoke should have been {0} but actual was {1}",
445+
expectedMethod.Name, invokedMethods[1]));
446+
}
447+
448+
[Fact]
449+
public static void Proxy_Declares_Interface_Events()
450+
{
451+
TestType_IEventService proxy = DispatchProxy.Create<TestType_IEventService, TestDispatchProxy>();
452+
EventInfo eventInfo = proxy.GetType().GetTypeInfo().GetDeclaredEvent("AddRemove");
453+
Assert.NotNull(eventInfo);
454+
}
455+
456+
457+
[Fact]
458+
public static void Invoke_Indexer_Setter_And_Getter_Invokes_Correct_Methods()
459+
{
460+
List<MethodInfo> invokedMethods = new List<MethodInfo>();
461+
462+
TestType_IIndexerService proxy = DispatchProxy.Create<TestType_IIndexerService, TestDispatchProxy>();
463+
((TestDispatchProxy)proxy).CallOnInvoke = (method, args) =>
464+
{
465+
invokedMethods.Add(method);
466+
return null;
467+
};
468+
469+
470+
proxy["key"] = "testValue";
471+
string actualValue = proxy["key"];
472+
473+
Assert.True(invokedMethods.Count == 2, String.Format("Expected 2 method invocations but received {0}", invokedMethods.Count));
474+
475+
PropertyInfo propertyInfo = typeof(TestType_IIndexerService).GetTypeInfo().GetDeclaredProperty("Item");
476+
Assert.NotNull(propertyInfo);
477+
478+
MethodInfo expectedMethod = propertyInfo.SetMethod;
479+
Assert.True(invokedMethods[0] != null && expectedMethod == invokedMethods[0], String.Format("First invoke should have been {0} but actual was {1}",
480+
expectedMethod.Name, invokedMethods[0]));
481+
482+
expectedMethod = propertyInfo.GetMethod;
483+
Assert.True(invokedMethods[1] != null && expectedMethod == invokedMethods[1], String.Format("Second invoke should have been {0} but actual was {1}",
484+
expectedMethod.Name, invokedMethods[1]));
485+
486+
Assert.Null(actualValue);
487+
}
488+
489+
[Fact]
490+
public static void Proxy_Declares_Interface_Indexers()
491+
{
492+
TestType_IIndexerService proxy = DispatchProxy.Create<TestType_IIndexerService, TestDispatchProxy>();
493+
PropertyInfo propertyInfo = proxy.GetType().GetTypeInfo().GetDeclaredProperty("Item");
494+
Assert.NotNull(propertyInfo);
495+
}
389496
}
390497
}

‎src/System.Reflection.DispatchProxy/tests/TestTypes.cs‎

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,18 @@ public interface TestType_IPropertyService
5656
string ReadWrite { get; set; }
5757
}
5858

59+
// Demonstrates proxies can be made for events.
60+
public interface TestType_IEventService
61+
{
62+
event EventHandler AddRemove;
63+
}
64+
65+
// Demonstrates proxies can be made for indexed properties.
66+
public interface TestType_IIndexerService
67+
{
68+
string this[string key] { get; set; }
69+
}
70+
5971
// Demonstrates proxies can be made for internal types
6072
internal interface TestType_InternalInterfaceService
6173
{

‎src/System.Reflection.DispatchProxy/tests/project.json‎

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,8 @@
77
"System.Linq.Expressions": "4.1.1-beta-24223-03",
88
"System.ObjectModel": "4.0.13-beta-24223-03",
99
"System.Reflection": "4.1.1-beta-24223-03",
10+
"System.Reflection.Emit": "4.0.2-beta-24223-03",
11+
"System.Reflection.Emit.ILGeneration": "4.0.2-beta-24223-03",
1012
"System.Runtime": "4.1.1-beta-24223-03",
1113
"System.Text.RegularExpressions": "4.2.0-beta-24223-03",
1214
"test-runtime": {
@@ -20,7 +22,6 @@
2022
"netstandard1.3": {}
2123
},
2224
"supports": {
23-
"coreFx.Test.netcore50": {},
2425
"coreFx.Test.netcoreapp1.0": {},
2526
"coreFx.Test.net46": {},
2627
"coreFx.Test.net461": {},

0 commit comments

Comments
 (0)