【问题标题】:DynamicMethod and type checks动态方法和类型检查
【发布时间】:2013-07-31 13:25:49
【问题描述】:

有人可以解释或指出为什么在下面的示例中没有发生运行时类型检查 - 字符串属性可以设置为任何类型值...
在一个非常意想不到的地方卡住了这个,真的很惊讶

using System;
using System.Reflection;
using System.Reflection.Emit;

namespace Dynamics
{
internal class Program
    {
    private static void Main(string[] args)
    {
        var a = new A();
        a.Name = "Name";
        Console.WriteLine(a.Name.GetType().Name);

        PropertyInfo pi = a.GetType().GetProperty("Name");          

        DynamicMethod method = new DynamicMethod(
                "DynamicSetValue", // NAME
                null, // return type
                new Type[] 
                            {
                                typeof(object), // 0, objSource
                                typeof(object), // 1, value
                            }, // parameter types
                typeof(Program), // owner
                true); // skip visibility

        ILGenerator gen = method.GetILGenerator();
        gen.Emit(OpCodes.Ldarg_0);
        gen.Emit(OpCodes.Ldarg_1);
        gen.Emit(OpCodes.Call, pi.GetSetMethod(true));
        gen.Emit(OpCodes.Ret);

        SetValue setMethod = (SetValue)method.CreateDelegate(typeof(SetValue));

        int val = 123;
        setMethod(a, val);
        Console.WriteLine(a.Name.GetType().Name);

        A anotherA = new A();
        anotherA.Name = "Another A";
        setMethod(a, anotherA);
        Console.WriteLine(a.Name.GetType().Name);
    }
}

public class A
{
    public string Name { get; set; }
}

public delegate void SetValue(object obj, object val);
}

【问题讨论】:

    标签: c# .net dynamicmethod


    【解决方案1】:

    我做了一个小实验:为你的类添加了一个方法:

        static void SetValue1(A a, object v)
        {
            a.Name = (string)v;
        }
    

    当然,SetValue1(a, 123); 会抛出 InvalidCastException。 然后我使用ildasm.exe 反汇编了代码。 SetValue1 看起来像这样:

    .method private hidebysig static void  SetValue1(class ConsoleApplication2.A a,
                                                       object v) cil managed
      {
        // Code size       15 (0xf)
        .maxstack  8
        IL_0000:  nop
        IL_0001:  ldarg.0
        IL_0002:  ldarg.1
        IL_0003:  castclass  [mscorlib]System.String // <--- replace this with nop
        IL_0008:  callvirt   instance void ConsoleApplication2.A::set_Name(string)
        IL_000d:  nop
        IL_000e:  ret
      } // end of method Program::SetValue1
    

    好的,让我们将 castclass [mscorlib]System.String 替换为 nop 并用 ilasm.exe 重新编译。

    现在使用错误类型参数调用 SetValue1 并产生与您的动态方法相同的结果。因此,在这种情况下,CLR 似乎没有进行类型检查。 documentation 说:

    在即时 (JIT) 编译期间,可选的验证过程会检查要被 JIT 编译为本机机器代码的方法的元数据和 Microsoft 中间语言 (MSIL),以验证它们是否是类型安全的。如果代码有绕过验证的权限,则跳过此过程。

    在这种情况下,我们在本地机器上运行代码,因此 CLR 相信 IL 是有效的。

    您可以通过在输出 .exe 文件上运行 peverify.exe 来手动验证程序集。它将返回错误:Program::SetValue1][offset 0x00000004][found ref 'System.Object'][expected ref 'System.String'] Unexpected type on the stack.

    有一篇很好的帖子探讨了这个话题:http://www.pcreview.co.uk/forums/net-type-safety-and-net-configuration-tool-t1225543.html

    【讨论】:

    • 这是正确的,也是一个很好的证明。 JIT 对于无法验证的 IL 没有根本问题。
    【解决方案2】:

    我认为这是因为您将参数声明为object(System.Object)。 intSystem.ValueType:System.ObjectA:System.Object.System.Object 是所有类的基类 (http://msdn.microsoft.com/en-us/library/system.object.aspx)。例如,如果将typeof(object) 更改为typeof(string),则会出现转换错误。

    编辑: 我认为您的示例中禁用了参数类型检查,因为您替换了属性 getter/setter 的调用。如果需要对调用动态方法进行类型检查,可以尝试使用下一个代码:

        var a = new A();
        a.Name = "Name";
        Console.WriteLine(a.Name.GetType().Name);
    
        PropertyInfo pi = a.GetType().GetProperty("Name");          
    
        DynamicMethod method = new DynamicMethod(
                "DynamicSetValue", // NAME
                null, // return type
                new Type[] 
                            {
                                typeof(Object), // 0, objSource
                                pi.PropertyType, // 1, value
                            }, // parameter types
                typeof(OracleUserOlapRepositoryTests), // owner
                true); // skip visibility
    
        ILGenerator gen = method.GetILGenerator();
        gen.Emit(OpCodes.Ldarg_0);
        gen.Emit(OpCodes.Ldarg_1);
        gen.Emit(OpCodes.Call, pi.GetSetMethod(true));
        gen.Emit(OpCodes.Ret);
    
       //correct  
       method.Invoke(a, new object[]{a,"test"});
    
       //error  
       method.Invoke(a, new object[]{a,new List<String>()});
    
       Console.WriteLine(a.Name.GetType().Name);
    

    【讨论】:

    • 实际上,在为 A.Name 分配一些值时,我应该进行类型检查,而不是通过方法参数输入。 pi.SetValue(a, 123) 将导致带有关于对象类型转换错误的文本的 ArgumentException,但 SetValue 方法也接受对象作为参数。
    猜你喜欢
    • 2013-09-14
    • 2021-01-15
    • 2011-07-02
    • 1970-01-01
    • 2021-12-25
    • 1970-01-01
    • 2020-04-03
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多