DynamicMethod and type checks

556 Views Asked by At

Can someone explain or point to explanation why runtime types check not occurs in sample below - string property can be set to any type value ...
Stuck with this in very unexpected place and was really surprised

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

There are 2 best solutions below

1
On

I think its because you declare params as object(System.Object). int is System.ValueType:System.Object and A:System.Object.System.Object is base class of all classes (http://msdn.microsoft.com/en-us/library/system.object.aspx). If you will change typeof(object) to typeof(string) for example, you will get cast error.

Edit: I think that params type cheking is disabled in your sample because you replace call of property getter/setter. If you need a type check for invoke dynamic method, you can try to use next code:

    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);
1
On

I did a little experiment: Added a method to your class:

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

Doing SetValue1(a, 123); throws InvalidCastException of course. I then disassembled the code using ildasm.exe. SetValue1 looks like this:

.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

Ok, let's replace the cast castclass [mscorlib]System.String with nop and recompile with ilasm.exe.

Now the call to SetValue1 with wrong type argument passes and produces the same result as your dynamic method. So it would seem that CLR is not doing type checking in this case. The documentation says:

During just-in-time (JIT) compilation, an optional verification process examines the metadata and Microsoft intermediate language (MSIL) of a method to be JIT-compiled into native machine code to verify that they are type safe. This process is skipped if the code has permission to bypass verification.

In this case, we are running code on local machine, so the CLR trusts that the IL is valid.

You can manually verify the assembyl by running peverify.exe on the output .exe file. It will return with an error: Program::SetValue1][offset 0x00000004][found ref 'System.Object'][expected ref 'System.String'] Unexpected type on the stack.

There is a very good post that explores this topic: http://www.pcreview.co.uk/forums/net-type-safety-and-net-configuration-tool-t1225543.html