DynamicMethod и проверки типов

Может кто-нибудь объяснить или указать на объяснение, почему проверка типов во время выполнения не происходит в приведенном ниже примере - строковое свойство может быть установлено на любое значение типа ...
Застрял с этим в очень неожиданном месте и был очень удивлен

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

person Alex A    schedule 31.07.2013    source источник


Ответы (2)


Я провел небольшой эксперимент: добавил в свой класс метод:

    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 не выполняет проверку типов. В документации говорится:

Во время JIT-компиляции необязательный процесс проверки проверяет метаданные и промежуточный язык Microsoft (MSIL) метода, который будет JIT-компилирован в собственный машинный код, чтобы убедиться, что они безопасны для типов. Этот процесс пропускается, если у кода есть разрешение на обход проверки.

В этом случае мы запускаем код на локальном компьютере, поэтому среда CLR считает, что IL действителен.

Вы можете вручную проверить сборку, запустив peverify.exe в выходном файле .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

person qbik    schedule 17.06.2014
comment
Это правильно и хорошее доказательство. У JIT нет фундаментальных проблем с непроверяемым IL. - person usr; 17.06.2014

Я думаю, это потому, что вы объявляете параметры как object(System.Object). int — это System.ValueType:System.Object, а A: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);
person Frank59    schedule 31.07.2013
comment
На самом деле я ожидал проверки типа при присвоении A.Name некоторого значения, а не через ввод параметров метода. pi.SetValue(a, 123) вызовет ArgumentException с текстом об ошибке преобразования типа объекта, но метод SetValue также принимает объекты в качестве параметров. - person Alex A; 31.07.2013