I'm quite sure I'm missing some constraint or caveat somewhere, but here's my situation. Assume I have a class that I want to have a proxy for, like the following:
public class MyList : MarshalByRefObject, IList<string>
{
private List<string> innerList;
public MyList(IEnumerable<string> stringList)
{
this.innerList = new List<string>(stringList);
}
// IList<string> implementation omitted for brevity.
// For the sake of this exercise, assume each method
// implementation merely passes through to the associated
// method on the innerList member variable.
}
I want to create a proxy for that class, so that I can intercept method calls and perform some processing on the underlying object. Here is my implementation:
public class MyListProxy : RealProxy
{
private MyList actualList;
private MyListProxy(Type typeToProxy, IEnumerable<string> stringList)
: base(typeToProxy)
{
this.actualList = new MyList(stringList);
}
public static object CreateProxy(IEnumerable<string> stringList)
{
MyListProxy listProxy = new MyListProxy(typeof(MyList), stringList);
object foo = listProxy.GetTransparentProxy();
return foo;
}
public override IMessage Invoke(IMessage msg)
{
IMethodCallMessage callMsg = msg as IMethodCallMessage;
MethodInfo proxiedMethod = callMsg.MethodBase as MethodInfo;
return new ReturnMessage(proxiedMethod.Invoke(actualList, callMsg.Args), null, 0, callMsg.LogicalCallContext, callMsg);
}
}
Finally, I have a class that consumes the proxied class, and I set the value of the MyList
member via reflection.
public class ListConsumer
{
public MyList MyList { get; protected set; }
public ListConsumer()
{
object listProxy = MyListProxy.CreateProxy(new List<string>() { "foo", "bar", "baz", "qux" });
PropertyInfo myListPropInfo = this.GetType().GetProperty("MyList");
myListPropInfo.SetValue(this, listProxy);
}
}
Now, if I try to use reflection to access the proxied object, I run into problems. Here is an example:
class Program
{
static void Main(string[] args)
{
ListConsumer listConsumer = new ListConsumer();
// These calls merely illustrate that the property can be
// properly accessed and methods called through the created
// proxy without issue.
Console.WriteLine("List contains {0} items", listConsumer.MyList.Count);
Console.WriteLine("List contents:");
foreach(string stringValue in listConsumer.MyList)
{
Console.WriteLine(stringValue);
}
Type listType = listConsumer.MyList.GetType();
foreach (Type interfaceType in listType.GetInterfaces())
{
if (interfaceType.IsGenericType && interfaceType.GetGenericTypeDefinition() == typeof(ICollection<>))
{
// Attempting to get the value of the Count property via
// reflection throws an exception.
Console.WriteLine("Checking interface {0}", interfaceType.Name);
System.Reflection.PropertyInfo propInfo = interfaceType.GetProperty("Count");
int count = (int)propInfo.GetValue(listConsumer.MyList, null);
}
else
{
Console.WriteLine("Skipping interface {0}", interfaceType.Name);
}
}
Console.ReadLine();
}
}
Attempting to call GetValue
on the Count
property via reflection throws the following exception:
An exception of type 'System.Reflection.TargetException' occurred in mscorlib.dll but was not handled in user code
Additional information: Object does not match target type.
When attempting to get the value of the Count
property, apparently the framework is calling down into System.Runtime.InteropServices.WindowsRuntime.IVector
to call the get_Size
method. I'm not understanding how this call fails on the underlying object of the proxy (the actual list) to make this happen. If I'm not using a proxy of the object, getting the property value works fine via reflection. What am I doing wrong? Can I even do what I'm trying to accomplish?
Edit: A bug has been opened regarding this issue at the Microsoft Connect site.
I think this may be a bug in the .Net framework. Somehow the RuntimePropertyInfo.GetValue
method is picking the wrong implementation for the ICollection<>.Count
property, and it appears to have to do with WindowsRuntime projections. Perhaps the remoting code was redone when they put the WindowsRuntime interop in the framework.
I switched the framework to target .Net 2.0 since I thought if this was a bug, it shouldn't be in that framework. When converting, Visual Studio removed the "Prefer 32 bit" check on my console exe project (since this doesn't exist in 2.0). It runs without exception when this is not present.
In summary, it runs on .Net 2.0 in both 32 and 64 bit. It runs on .Net 4.x in 64 bit. The exception is thrown on .Net 4.x 32 bit only. This sure looks like a bug. If you can run it 64-bit, that would be a workaround.
Note that I've installed .Net 4.6, and this replaces much of the .Net framework v4.x. It could be this is where the problem is introduced; I can't test until I get a machine that doesn't have .Net 4.6.
Update: 2015-09-08
It also happens on a machine with only .Net 4.5.2 installed (no 4.6).
Update: 2015-09-07
Here's a smaller repro, using your same classes:
static void Main(string[] args)
{
var myList = MyListProxy.CreateProxy(new[] {"foo", "bar", "baz", "quxx"});
var listType = myList.GetType();
var interfaceType = listType.GetInterface("System.Collections.Generic.ICollection`1");
var propInfo = interfaceType.GetProperty("Count");
// TargetException thrown on 32-bit .Net 4.5.2+ installed
int count = (int)propInfo.GetValue(myList, null);
}
I've also tried the IsReadOnly
property, but it appears to work (no exception).
As to the source of the bug, there are two layers of indirection around properties, one being the remoting, and the other being a mapping of metadata structures called MethodDef
s with the actual runtime method, known internally as a MethodDesc
. This mapping is specialized for properties (as well as events), where additional MethodDesc
s to support the property's get/set PropertyInfo instances are known as Associates
. By calling PropertyInfo.GetValue
we go through one of these Associate MethodDesc
pointers to the underlying method implementation, and remoting does some pointer math to get the correct MethodDesc
on the other side of the channel. The CLR code is very convoluted here, and I don't have enough experience of the in-memory layout of the MethodTable
which holds these MethodDesc
records which remoting uses (or the mapping it uses to get to the MethodTable?), but I'd say it's a fair guess that remoting is grabbing the wrong MethodDesc
via some bad pointer math. That's why we see a similar but unrelated (as far as your program) MethodDesc
- UInt32 get_Size
of IVector<T>
being invoked on the call:
System.Reflection.RuntimeMethodInfo.CheckConsistency(Object target)
System.Reflection.RuntimeMethodInfo.InvokeArgumentsCheck(Object obj, BindingFlags invokeAttr, Binder binder, Object[] parameters, CultureInfo culture)
System.Reflection.RuntimeMethodInfo.Invoke(Object obj, BindingFlags invokeAttr, Binder binder, Object[] parameters, CultureInfo culture)
System.Reflection.MethodBase.Invoke(Object obj, Object[] parameters)
ConsoleApplication1.MyListProxy.Invoke(IMessage msg) Program.cs: line: 60
System.Runtime.Remoting.Proxies.RealProxy.PrivateInvoke(MessageData& msgData, Int32 type)
System.Runtime.InteropServices.WindowsRuntime.IVector`1.get_Size()
System.Runtime.InteropServices.WindowsRuntime.VectorToCollectionAdapter.Count[T]()