我可以在RealProxy实例中使用反射吗?

Jim*_*ans 26 c# reflection clr proxy-classes

我很确定我在某个地方缺少一些约束或警告,但这是我的情况.假设我有一个我想拥有代理的类,如下所示:

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.
}
Run Code Online (Sandbox Code Playgroud)

我想为该类创建一个代理,以便我可以拦截方法调用并对底层对象执行一些处理.这是我的实现:

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);
    }
}
Run Code Online (Sandbox Code Playgroud)

最后,我有一个使用代理类的类,我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);
    }
}
Run Code Online (Sandbox Code Playgroud)

现在,如果我尝试使用反射来访问代理对象,我会遇到问题.这是一个例子:

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();
    }
}
Run Code Online (Sandbox Code Playgroud)

试图调用GetValueCount通过反射属性会引发以下异常:

mscorlib.dll中出现"System.Reflection.TargetException"类型的异常,但未在用户代码中处理

附加信息:对象与目标类型不匹配.

当试图获取Count属性的值时,显然框架正在调用System.Runtime.InteropServices.WindowsRuntime.IVector以调用该get_Size方法.我不明白这个调用如何在代理的底层对象(实际列表)上失败以实现这一点.如果我没有使用对象的代理,通过反射获取属性值可以正常工作.我究竟做错了什么?我甚至可以做我想要完成的事情吗?

编辑: Microsoft Connect站点上已针对此问题打开了一个错误.

cod*_*zen 11

我认为这可能是.Net框架中的一个错误.不知何故,该RuntimePropertyInfo.GetValue方法为该ICollection<>.Count属性选择了错误的实现,它似乎与WindowsRuntime投影有关.当他们将WindowsRuntime互操作放在框架中时,也许重做了远程代码.

我将框架切换到目标.Net 2.0因为我认为这是一个bug,它不应该在那个框架中.转换时,Visual Studio删除了我的控制台exe项目上的"首选32位"检查(因为这在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

这是一个较小的repro,使用相同的类:

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); 
}
Run Code Online (Sandbox Code Playgroud)

我也尝试了这个IsReadOnly属性,但似乎有效(也不例外).


至于bug的来源,有两层属性间接,一个是远程处理,另一个是MethodDef使用实际运行时方法称为s 的元数据结构的映射,内部称为a MethodDesc.此映射专用于属性(以及事件),其中MethodDesc支持属性的get/set PropertyInfo实例的附加s称为Associates.通过调用PropertyInfo.GetValue我们通过这些Associate MethodDesc指针中的一个指向底层方法实现,并且远程处理执行一些指针数学运算以MethodDesc在通道的另一侧获得正确.这里的CLR代码非常复杂,我没有足够的内存布局经验MethodTable持有这些MethodDesc远程使用的记录(或它用于获取MethodTable的映射?),但我会说这是一个公平的猜测,远程处理MethodDesc通过一些糟糕的指针数学来抓取错误.这就是为什么我们看到了类似但不相关的(只要你的程序)MethodDesc- UInt32 get_SizeIVector<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]()
Run Code Online (Sandbox Code Playgroud)


Han*_*ant 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 <>的实现.Count可以是正常实现它的.NET类,也可以是将其公开为IVector <>.大小的WinRT对象.很明显,你创建的代理让它头疼,它错误地决定了WinRT版本.

它是如何应该找出哪些是正确的选择是很明朗.毕竟,您的代理可以是实际WinRT对象的代理,然后它所做的选择是正确的.这很可能是一个结构性问题.它的行为如此随机,代码在64位模式下工作,并不完全鼓舞人心.VectorToCollectionAdapter非常危险,请注意JitHelpers.UnsafeCast调用,这个bug可能是可利用的.

好吧,提醒当局,在connect.microsoft.com上提交错误报告.如果你不想花时间让我知道,我会照顾它.很难找到一种解决方法,使用以WinRT为中心的TypeInfo类来进行反射没有任何区别.去除抖动强制使其以64位模式运行是一种创可贴,但几乎不能保证.