c# - 我可以对 RealProxy 实例使用反射吗?

标签 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.
}

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

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();
    }
}

尝试通过反射调用 Count 属性上的 GetValue 会引发以下异常:

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.

当尝试获取 Count 属性的值时,显然框架正在向下调用 System.Runtime.InteropServices.WindowsRuntime.IVector 以调用 get_Size 方法。我不明白此调用如何在代理的基础对象(实际列表)上失败以实现此目的。如果我不使用对象的代理,则通过反射获取属性值可以正常工作。我究竟做错了什么?我什至可以做我想完成的事情吗?

编辑 A bug has been opened有关此问题的信息,请访问 Microsoft Connect 站点。

最佳答案

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

我将框架切换到目标 .Net 2.0,因为我认为如果这是一个错误,它不应该在那个框架中。转换时,Visual Studio 删除了我的控制台 exe 项目上的“首选 32 位”检查(因为这在 2.0 中不存在)。当它不存在时,它会毫无异常(exception)地运行。

总而言之,它在 .Net 2.0 上以 32 位和 64 位运行。它在 64 位的 .Net 4.x 上运行。仅在 .Net 4.x 32 位上抛出异常。这肯定看起来像一个错误。如果您可以在 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属性,但它似乎有效(无一异常(exception))。


至于错误的来源,围绕属性有两层间接层,一层是远程处理,另一层是称为 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在 channel 的另一边。这里的 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]()

关于c# - 我可以对 RealProxy 实例使用反射吗?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/32359914/

相关文章:

c# - C# 中的 fork 概念

C# MS Access oledbcommand.executeNonQuery UPDATE 没有错误,没有变化

c# - 如何使用 C# 在 Windows 中检测网络位置类型?

c# - 如何从一个类中读取另一个类中的文档?

c# - 使用 .net 4.0 测试程序集测试 .net 3.5 程序集时的风险有多大

.net - .NET 2.0 运行时上的 LINQ

c# - SaveFileDialog 将文件夹保存在内存中

c# - 将 Func<T, String> 转换为 Func<T, bool>

java - 在运行时,查找 Java 应用程序中扩展基类的所有类

Java - 我是否可以反省地发现我是否正在从 catch block 中的函数调用以获取特定异常?