我很确定我在某个地方缺少一些约束或警告,但这是我的情况。假设我有一个我想拥有代理的类,如下所示:
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.
}
我想为该类创建一个代理,以便我可以拦截方法调用并对底层对象执行一些处理。这是我的实施:
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);
}
}
最后,我有一个使用代理类的类,并通过反射设置MyList
成员的值。
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);
}
}
现在,如果我尝试使用反射来访问代理对象,我会遇到问题。这是一个例子:
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();
}
}
尝试通过反射调用GetValue
属性上的Count
会引发以下异常:
发生了'System.Reflection.TargetException'类型的异常 mscorlib.dll但未在用户代码中处理
其他信息:对象与目标类型不匹配。
当试图获取Count
属性的值时,显然框架调用System.Runtime.InteropServices.WindowsRuntime.IVector
来调用get_Size
方法。我不明白这个调用如何在代理的底层对象(实际列表)上失败以实现这一点。如果我没有使用对象的代理,通过反射获取属性值可以正常工作。我究竟做错了什么?我甚至可以做我想要完成的事情吗?
编辑:在Microsoft Connect网站上针对此问题发出bug has been opened。
答案 0 :(得分:11)
我认为这可能是.Net框架中的一个错误。不知怎的,RuntimePropertyInfo.GetValue
方法为ICollection<>.Count
属性选择了错误的实现,它似乎与WindowsRuntime投影有关。当他们将WindowsRuntime互操作放在框架中时,也许重做了远程代码。
我将框架切换到目标.Net 2.0,因为我认为如果这是一个错误,它不应该在该框架中。转换时,Visual Studio删除了&#34;首选32位&#34;检查我的控制台exe项目(因为它不存在于2.0中)。如果不存在,它会毫无例外地运行。
总之,它在32位和64位上运行.Net 2.0。它以64位运行.Net 4.x.仅在.Net 4.x 32位上引发异常。这确实看起来像一个bug。如果你可以运行它64位,那将是一种解决方法。
请注意,我已经安装了.Net 4.6,这取代了.Net framework v4.x的大部分内容。这可能是引入问题的地方;我无法测试,直到我得到一台没有.Net 4.6的机器。
更新:2015-09-08
它也发生在只安装了.Net 4.5.2的机器上(没有4.6)。
更新:2015-09-07
这是一个较小的复制品,使用相同的类:
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);
}
我还尝试了IsReadOnly
属性,但似乎有效(也不例外)。
关于bug的来源,属性有两层间接,一个是远程处理,另一个是名为MethodDef
的元数据结构与实际运行时方法的映射,内部称为一个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
。通过调用PropertyInfo.GetValue
,我们通过其中一个指向底层方法实现的Associate MethodDesc
指针,并且远程处理会执行一些指针数学运算,以便在通道的另一侧获得正确的MethodDesc
。这里的CLR代码非常复杂,我没有足够的MethodTable
内存布局经验,它包含了远程使用的这些MethodDesc
记录(或者它用于映射的映射)转到MethodTable?),但我说它是一个公平的猜测,远程通过一些糟糕的指针数学来抓取错误的MethodDesc
。这就是为什么我们会在通话中调用MethodDesc
UInt32 get_Size
的{{1}} - IVector<T>
看到类似但不相关的内容(就您的计划而言):
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]()
答案 1 :(得分:11)
这是一个非常有趣的CLR错误,其中一些内容正在显示在不幸事故中。您可以从堆栈跟踪中判断它正在尝试调用VectorToCollectionAdapter的Count属性。
这个类很特别,没有创建它的实例。它是.NET 4.5中添加的语言投影的一部分,它使WinRT接口类型看起来像.NET Framework类型。它非常类似于SZArrayHelper类,这是一个适配器类,有助于实现非泛型数组实现通用接口类型(如IList<T>
)的错觉。
此处的接口映射适用于WinRT IVector<T>
接口。如MSDN文章中所述,该接口类型映射到IList<T>
。内部VectorToListAdapter类负责IList<T>
成员,VectorToCollectionAdapter处理ICollection<T>
成员。
您的代码强制CLR查找ICollection&lt;&gt; .Count的实现,它可以是正常实现它的.NET类,也可以是将其公开为IVector&lt;&gt; .Size的WinRT对象。很明显,你创建的代理使它头疼,它错误地决定了WinRT版本。
如何假设找出哪个是正确的选择是非常模糊的。毕竟,您的代理可能是实际WinRT对象的代理,然后它所做出的选择将是正确的。这很可能是一个结构性问题。它的行为如此随机,代码在64位模式下工作,并不完全鼓舞人心。 VectorToCollectionAdapter非常危险,请注意JitHelpers.UnsafeCast调用,这个bug有可能被利用。
好吧,提醒当局,在connect.microsoft.com上提交错误报告。如果你不想花时间让我知道,我会照顾它。很难找到一种解决方法,使用以WinRT为中心的TypeInfo类来进行反射没有任何区别。去除抖动强制使其以64位模式运行是一种创可贴,但几乎不能保证。