Permalink
Browse files

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#.
  • Loading branch information...
yfakariya committed Jun 18, 2016
1 parent 6c36a16 commit e8f650d67a67003786c04b916dc4805678237c0a
@@ -426,13 +426,78 @@ internal void AddInterfaceImpl(Type iface)
_assembly.EnsureTypeIsVisible(iface);
_tb.AddInterfaceImplementation(iface);
+
+ // AccessorMethods -> Metadata mappings.
+ var propertyMap = new Dictionary<MethodInfo, PropertyAccessorInfo>(MethodInfoEqualityComparer.Instance);
+ foreach (PropertyInfo pi in iface.GetRuntimeProperties())
+ {
+ var ai = new PropertyAccessorInfo(pi.GetMethod, pi.SetMethod);
+ if (pi.GetMethod != null)
+ propertyMap[pi.GetMethod] = ai;
+ if (pi.SetMethod != null)
+ propertyMap[pi.SetMethod] = ai;
+ }
+
+ var eventMap = new Dictionary<MethodInfo, EventAccessorInfo>(MethodInfoEqualityComparer.Instance);
+ foreach (EventInfo ei in iface.GetRuntimeEvents())
+ {
+ var ai = new EventAccessorInfo(ei.AddMethod, ei.RemoveMethod, ei.RaiseMethod);
+ if (ei.AddMethod != null)
+ eventMap[ei.AddMethod] = ai;
+ if (ei.RemoveMethod != null)
+ eventMap[ei.RemoveMethod] = ai;
+ if (ei.RaiseMethod != null)
+ eventMap[ei.RaiseMethod] = ai;
+ }
+
foreach (MethodInfo mi in iface.GetRuntimeMethods())
{
- AddMethodImpl(mi);
+ MethodBuilder mdb = AddMethodImpl(mi);
+ PropertyAccessorInfo associatedProperty;
+ if (propertyMap.TryGetValue(mi, out associatedProperty))
+ {
+ if (MethodInfoEqualityComparer.Instance.Equals(associatedProperty.InterfaceGetMethod, mi))
+ associatedProperty.GetMethodBuilder = mdb;
+ else
+ associatedProperty.SetMethodBuilder = mdb;
+ }
+
+ EventAccessorInfo associatedEvent;
+ if (eventMap.TryGetValue(mi, out associatedEvent))
+ {
+ if (MethodInfoEqualityComparer.Instance.Equals(associatedEvent.InterfaceAddMethod, mi))
+ associatedEvent.AddMethodBuilder = mdb;
+ else if (MethodInfoEqualityComparer.Instance.Equals(associatedEvent.InterfaceRemoveMethod, mi))
+ associatedEvent.RemoveMethodBuilder = mdb;
+ else
+ associatedEvent.RaiseMethodBuilder = mdb;
+ }
+ }
+
+ foreach (PropertyInfo pi in iface.GetRuntimeProperties())
+ {
+ PropertyAccessorInfo ai = propertyMap[pi.GetMethod ?? pi.SetMethod];
+ PropertyBuilder pb = _tb.DefineProperty(pi.Name, pi.Attributes, pi.PropertyType, pi.GetIndexParameters().Select(p => p.ParameterType).ToArray());
+ if (ai.GetMethodBuilder != null)
+ pb.SetGetMethod(ai.GetMethodBuilder);
+ if (ai.SetMethodBuilder != null)
+ pb.SetSetMethod(ai.SetMethodBuilder);
+ }
+
+ foreach (EventInfo ei in iface.GetRuntimeEvents())
+ {
+ EventAccessorInfo ai = eventMap[ei.AddMethod ?? ei.RemoveMethod];
+ EventBuilder eb = _tb.DefineEvent(ei.Name, ei.Attributes, ei.EventHandlerType);
+ if (ai.AddMethodBuilder != null)
+ eb.SetAddOnMethod(ai.AddMethodBuilder);
+ if (ai.RemoveMethodBuilder != null)
+ eb.SetRemoveOnMethod(ai.RemoveMethodBuilder);
+ if (ai.RaiseMethodBuilder != null)
+ eb.SetRaiseMethod(ai.RaiseMethodBuilder);
}
}
- private void AddMethodImpl(MethodInfo mi)
+ private MethodBuilder AddMethodImpl(MethodInfo mi)
{
ParameterInfo[] parameters = mi.GetParameters();
Type[] paramTypes = ParamTypes(parameters, false);
@@ -541,6 +606,7 @@ private void AddMethodImpl(MethodInfo mi)
il.Emit(OpCodes.Ret);
_tb.DefineMethodOverride(mdb, mi);
+ return mdb;
}
private static Type[] ParamTypes(ParameterInfo[] parms, bool noByRef)
@@ -831,6 +897,112 @@ internal void EndSet(Type stackType)
_il.Emit(OpCodes.Stelem_Ref);
}
}
+
+ private sealed class PropertyAccessorInfo
+ {
+ public MethodInfo InterfaceGetMethod { get; }
+ public MethodInfo InterfaceSetMethod { get; }
+ public MethodBuilder GetMethodBuilder { get; set; }
+ public MethodBuilder SetMethodBuilder { get; set; }
+
+ public PropertyAccessorInfo(MethodInfo interfaceGetMethod, MethodInfo interfaceSetMethod)
+ {
+ InterfaceGetMethod = interfaceGetMethod;
+ InterfaceSetMethod = interfaceSetMethod;
+ }
+ }
+
+ private sealed class EventAccessorInfo
+ {
+ public MethodInfo InterfaceAddMethod { get; }
+ public MethodInfo InterfaceRemoveMethod { get; }
+ public MethodInfo InterfaceRaiseMethod { get; }
+ public MethodBuilder AddMethodBuilder { get; set; }
+ public MethodBuilder RemoveMethodBuilder { get; set; }
+ public MethodBuilder RaiseMethodBuilder { get; set; }
+
+ public EventAccessorInfo(MethodInfo interfaceAddMethod, MethodInfo interfaceRemoveMethod, MethodInfo interfaceRaiseMethod)
+ {
+ InterfaceAddMethod = interfaceAddMethod;
+ InterfaceRemoveMethod = interfaceRemoveMethod;
+ InterfaceRaiseMethod = interfaceRaiseMethod;
+ }
+ }
+
+ private sealed class MethodInfoEqualityComparer : EqualityComparer<MethodInfo>
+ {
+ public static readonly MethodInfoEqualityComparer Instance = new MethodInfoEqualityComparer();
+
+ private MethodInfoEqualityComparer() { }
+
+ public sealed override bool Equals(MethodInfo left, MethodInfo right)
+ {
+ if (ReferenceEquals(left, right))
+ return true;
+
+ if (left == null)
+ return right == null;
+ else if (right == null)
+ return false;
+
+ // This assembly should work in netstandard1.3,
+ // so we cannot use MemberInfo.MetadataToken here.
+ // Therefore, it compares honestly referring ECMA-335 I.8.6.1.6 Signature Matching.
+ if (!Equals(left.DeclaringType, right.DeclaringType))
+ return false;
+
+ if (!Equals(left.ReturnType, right.ReturnType))
+ return false;
+
+ if (left.CallingConvention != right.CallingConvention)
+ return false;
+
+ if (left.IsStatic != right.IsStatic)
+ return false;
+
+ if ( left.Name != right.Name)
+ return false;
+
+ Type[] leftGenericParameters = left.GetGenericArguments();
+ Type[] rightGenericParameters = right.GetGenericArguments();
+ if (leftGenericParameters.Length != rightGenericParameters.Length)
+ return false;
+
+ for (int i = 0; i < leftGenericParameters.Length; i++)
+ {
+ if (!Equals(leftGenericParameters[i], rightGenericParameters[i]))
+ return false;
+ }
+
+ ParameterInfo[] leftParameters = left.GetParameters();
+ ParameterInfo[] rightParameters = right.GetParameters();
+ if (leftParameters.Length != rightParameters.Length)
+ return false;
+
+ for (int i = 0; i < leftParameters.Length; i++)
+ {
+ if (!Equals(leftParameters[i].ParameterType, rightParameters[i].ParameterType))
+ return false;
+ }
+
+ return true;
+ }
+
+ public sealed override int GetHashCode(MethodInfo obj)
+ {
+ if (obj == null)
+ return 0;
+
+ int hashCode = obj.DeclaringType.GetHashCode();
+ hashCode ^= obj.Name.GetHashCode();
+ foreach (ParameterInfo parameter in obj.GetParameters())
+ {
+ hashCode ^= parameter.ParameterType.GetHashCode();
+ }
+
+ return hashCode;
+ }
+ }
}
}
}
@@ -6,6 +6,7 @@
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
+using System.Reflection.Emit;
using System.Text;
using Xunit;
@@ -381,10 +382,116 @@ public static void Invoke_Property_Setter_And_Getter_Invokes_Correct_Methods()
expectedMethod.Name, invokedMethods[0]));
expectedMethod = propertyInfo.GetMethod;
- Assert.True(invokedMethods[1] != null && expectedMethod == invokedMethods[1], String.Format("First invoke should have been {0} but actual was {1}",
+ Assert.True(invokedMethods[1] != null && expectedMethod == invokedMethods[1], String.Format("Second invoke should have been {0} but actual was {1}",
expectedMethod.Name, invokedMethods[1]));
Assert.Null(actualValue);
}
+
+
+ [Fact]
+ public static void Proxy_Declares_Interface_Properties()
+ {
+ TestType_IPropertyService proxy = DispatchProxy.Create<TestType_IPropertyService, TestDispatchProxy>();
+ PropertyInfo propertyInfo = proxy.GetType().GetTypeInfo().GetDeclaredProperty("ReadWrite");
+ Assert.NotNull(propertyInfo);
+ }
+
+ [Fact]
+ public static void Invoke_Event_Add_And_Remove_And_Raise_Invokes_Correct_Methods()
+ {
+ // 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);
+ 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();
+
+ List<MethodInfo> invokedMethods = new List<MethodInfo>();
+ object proxy =
+ typeof(DispatchProxy)
+ .GetRuntimeMethod("Create", Array.Empty<Type>()).MakeGenericMethod(ieventServiceTypeInfo.AsType(), typeof(TestDispatchProxy))
+ .Invoke(null, null);
+ ((TestDispatchProxy)proxy).CallOnInvoke = (method, args) =>
+ {
+ invokedMethods.Add(method);
+ return null;
+ };
+
+ EventHandler handler = new EventHandler((sender, e) => {});
+
+ proxy.GetType().GetRuntimeMethods().Single(m => m.Name == "add_AddRemoveRaise").Invoke(proxy, new object[] { handler });
+ proxy.GetType().GetRuntimeMethods().Single(m => m.Name == "raise_AddRemoveRaise").Invoke(proxy, new object[] { EventArgs.Empty });
+ proxy.GetType().GetRuntimeMethods().Single(m => m.Name == "remove_AddRemoveRaise").Invoke(proxy, new object[] { handler });
+
+ Assert.True(invokedMethods.Count == 3, String.Format("Expected 3 method invocations but received {0}", invokedMethods.Count));
+
+ EventInfo eventInfo = ieventServiceTypeInfo.GetDeclaredEvent("AddRemoveRaise");
+ Assert.NotNull(eventInfo);
+
+ MethodInfo expectedMethod = eventInfo.AddMethod;
+ Assert.True(invokedMethods[0] != null && expectedMethod == invokedMethods[0], String.Format("First invoke should have been {0} but actual was {1}",
+ expectedMethod.Name, invokedMethods[0]));
+
+ expectedMethod = eventInfo.RaiseMethod;
+ Assert.True(invokedMethods[1] != null && expectedMethod == invokedMethods[1], String.Format("Second invoke should have been {0} but actual was {1}",
+ expectedMethod.Name, invokedMethods[1]));
+
+ expectedMethod = eventInfo.RemoveMethod;
+ Assert.True(invokedMethods[2] != null && expectedMethod == invokedMethods[2], String.Format("Third invoke should have been {0} but actual was {1}",
+ expectedMethod.Name, invokedMethods[1]));
+ }
+
+ [Fact]
+ public static void Proxy_Declares_Interface_Events()
+ {
+ TestType_IEventService proxy = DispatchProxy.Create<TestType_IEventService, TestDispatchProxy>();
+ EventInfo eventInfo = proxy.GetType().GetTypeInfo().GetDeclaredEvent("AddRemove");
+ Assert.NotNull(eventInfo);
+ }
+
+
+ [Fact]
+ public static void Invoke_Indexer_Setter_And_Getter_Invokes_Correct_Methods()
+ {
+ List<MethodInfo> invokedMethods = new List<MethodInfo>();
+
+ TestType_IIndexerService proxy = DispatchProxy.Create<TestType_IIndexerService, TestDispatchProxy>();
+ ((TestDispatchProxy)proxy).CallOnInvoke = (method, args) =>
+ {
+ invokedMethods.Add(method);
+ return null;
+ };
+
+
+ proxy["key"] = "testValue";
+ string actualValue = proxy["key"];
+
+ Assert.True(invokedMethods.Count == 2, String.Format("Expected 2 method invocations but received {0}", invokedMethods.Count));
+
+ PropertyInfo propertyInfo = typeof(TestType_IIndexerService).GetTypeInfo().GetDeclaredProperty("Item");
+ Assert.NotNull(propertyInfo);
+
+ MethodInfo expectedMethod = propertyInfo.SetMethod;
+ Assert.True(invokedMethods[0] != null && expectedMethod == invokedMethods[0], String.Format("First invoke should have been {0} but actual was {1}",
+ expectedMethod.Name, invokedMethods[0]));
+
+ expectedMethod = propertyInfo.GetMethod;
+ Assert.True(invokedMethods[1] != null && expectedMethod == invokedMethods[1], String.Format("Second invoke should have been {0} but actual was {1}",
+ expectedMethod.Name, invokedMethods[1]));
+
+ Assert.Null(actualValue);
+ }
+
+ [Fact]
+ public static void Proxy_Declares_Interface_Indexers()
+ {
+ TestType_IIndexerService proxy = DispatchProxy.Create<TestType_IIndexerService, TestDispatchProxy>();
+ PropertyInfo propertyInfo = proxy.GetType().GetTypeInfo().GetDeclaredProperty("Item");
+ Assert.NotNull(propertyInfo);
+ }
}
}
@@ -56,6 +56,18 @@ public interface TestType_IPropertyService
string ReadWrite { get; set; }
}
+// Demonstrates proxies can be made for events.
+public interface TestType_IEventService
+{
+ event EventHandler AddRemove;
+}
+
+// Demonstrates proxies can be made for indexed properties.
+public interface TestType_IIndexerService
+{
+ string this[string key] { get; set; }
+}
+
// Demonstrates proxies can be made for internal types
internal interface TestType_InternalInterfaceService
{
@@ -7,6 +7,8 @@
"System.Linq.Expressions": "4.1.1-beta-24223-03",
"System.ObjectModel": "4.0.13-beta-24223-03",
"System.Reflection": "4.1.1-beta-24223-03",
+ "System.Reflection.Emit": "4.0.2-beta-24223-03",
+ "System.Reflection.Emit.ILGeneration": "4.0.2-beta-24223-03",
"System.Runtime": "4.1.1-beta-24223-03",
"System.Text.RegularExpressions": "4.2.0-beta-24223-03",
"test-runtime": {
@@ -20,7 +22,6 @@
"netstandard1.3": {}
},
"supports": {
- "coreFx.Test.netcore50": {},
"coreFx.Test.netcoreapp1.0": {},
"coreFx.Test.net46": {},
"coreFx.Test.net461": {},

0 comments on commit e8f650d

Please sign in to comment.