Имена переменных и проверка типов во время Runtime

228
30 июля 2018, 19:50

Товарищи, возникли некоторые вопросы по поводу runtime, связанные с именами переменных и проверкой их типов, однако для лучшего понимания распишу всю ситуацию.

В одном из моих вопросов я приводил пример подмены методов во время runtime. Чуть "поигравшись" с этим, я обнаружил, что ключевое слово this в рамках инъецированного метода ссылается именно на тот объект, в класс которого он и был инъецирован. Я решил проверить, будет ли это работать и с переменными

Так что я создал тестовый класс:

public class TestClass
{
    public int Value { get; set; }
}

И класс для подмены:

public class InjectorClass
{
    public int Value { get; set; }
    public void SetterInjecter(int Data)
    {
        Value = ~Data;
        Console.WriteLine("Hello from injected setter!");
    }
}

Далее я произвел замену:

Type testType = typeof(TestClass);
Type injectType = typeof(InjectorClass);
Injector.Inject(testType.GetMethod("set_Value"), injectType.GetMethod("SetterInjecter"));
TestClass test = new TestClass();
test.Value = int.MinValue;
Console.WriteLine("test.Value == {0}", test.Value);

И все это действительно сработало, ибо я получил следующий вывод:

Hello from injected setter!
test.Value == 2147483647

(~a = -a - 1 => ~int.MinValue = int.MaxValue = 2147483647)

Тем самым мы убедились, что можем получить доступ к переменной из инъецированного метода по имени

После этого я пошел дальше и решил изменить тип переменной Value в классе InjectorClass, тем самым посмотрев, что получится при присвоении переменной типа int значения совершенного иного типа. Для этого я создал такую структуру:

[StructLayout(LayoutKind.Explicit, Size = 4)]
public struct IntByte
{
    [FieldOffset(0), MarshalAs(UnmanagedType.I1)]
    public byte _0;
    [FieldOffset(1), MarshalAs(UnmanagedType.I1)]
    public byte _1;
    [FieldOffset(2), MarshalAs(UnmanagedType.I1)]
    public byte _2;
    [FieldOffset(3), MarshalAs(UnmanagedType.I1)]
    public byte _3;
    public IntByte(int From)
    {
        string bits = Convert.ToString(From, 2).PadLeft(32, '0');
        _3 = Convert.ToByte(bits.Substring(0, 8), 2);
        _2 = Convert.ToByte(bits.Substring(8, 8), 2);
        _1 = Convert.ToByte(bits.Substring(16, 8), 2);
        _0 = Convert.ToByte(bits.Substring(24, 8), 2);
    }
}

Ее суть в том, что в памяти она выглядит ровно как заданный в инициализаторе int From.

Также я чуть изменил InjectorClass:

public class InjectorClass
{
    public IntByte Value { get; set; }
    public void SetterInjecter(int Data)
    {
        Value = new IntByte(~Data);
        Console.WriteLine("Hello from injected setter!\n\nbyte 0: {0};\nbyte 1: {1};\nbyte 2: {2};\nbyte 3: {3};\n\n", Value._0, Value._1, Value._2, Value._3);
    }
}

Тем самым получается, что в runtime после подмены мы присваиваем переменной типа int значение переменной типа IntByte. Запустив код вновь (код подмены не менялся с прошлого пункта), я ожидал увидеть крах приложения, сообщение об ошибке или хоть какой сигнализатор того, что я совершаю что-то неправомерное. Однако в консоли я вновь видел вполне себе веселый и бодрый вывод:

Hello from injected setter!

byte 0: 255;
byte 1: 255;
byte 2: 255;
byte 3: 127;

test.Value == 2147483647

Такое поведение мне показалось малость странным, ибо я всегда считал, что проверка типов ведется даже в runtime, т.к., скажем, такой код:

dynamic a = new IntByte(1);
int b = a;

Пусть он и скомпилируется, однако все же выбросит ошибку во время выполнения...

Едем дальше. Хорошо. Пускай типы уже не проверяются, но если мы, скажем, сошлемся на переменную, которой не существует в текущем экземпляре, ошибка ведь вылетит, правда? Нет. Как ни странно, поменяв InjectorClass на это:

public class InjectorClass
{
    public IntByte X { get; set; }
    public void SetterInjecter(int Data)
    {
        X = new IntByte(~Data);
        Console.WriteLine("Hello from injected setter!\n\nbyte 0: {0};\nbyte 1: {1};\nbyte 2: {2};\nbyte 3: {3};\n\n", X._0, X._1, X._2, X._3);
    }
}

Я все равно получил тот же самый вывод:

Hello from injected setter!

byte 0: 255;
byte 1: 255;
byte 2: 255;
byte 3: 127;

test.Value == 2147483647

Вот тут я уже впал в окончательную задумчивость, что же там в runtime творится.

К слову, если добавить в InjectorClass переменную с названием Value или типом int, то изменение X в инъецированном методе перестает влиять на test.Value, так что какая-то логика там точно присутствует

Собственно, пора бы и резюмировать возникшие у меня вопросы по данному поводу:

  1. Почему в указанном примере не ведется проверки типов переменных в runtime, если аналогичный по своей сути код с использованием dynamic все же падает во время исполнения?
  2. Почему свойство экземпляра класса, в который был инъецирован метод, ссылающийся на свойство с другим именем, все же меняет свое значение?
  3. Обратив внимание на структуру IntByte, вы заметите, что присвоение байтов идет с конца. С последнего к нулевому. То есть последовательность байтов для int.MaxValue выглядит как 255 -> 255 -> 255 -> 127, хотя по логике все должно быть наоборот. С чем связан такой обратный порядок значащих байтов? Сталкивался с этим, когда в памяти обрабатывал изображения в виде массива байтов, однако просто принимал как должное)


Буду очень благодарен за разъяснение этих интересных моментов)

READ ALSO
WPF Постепенная подгрузка в datagrid из БД

WPF Постепенная подгрузка в datagrid из БД

Добрый день у меня есть контекст Library, таблица BooksС помощью метода Load() я загружаю в Local данные и потом local привязываю к datagrid

206
C# WPF XAML как прописать путь к папке приложения?

C# WPF XAML как прописать путь к папке приложения?

Как в XAML прописать путь к картинке через EnvironmentCurrentDirectory

207
Выводить escape символы в file_get_contents

Выводить escape символы в file_get_contents

Есть файл, и его содержимое примерно такое:

202
Wordpress Menu Walker Class

Wordpress Menu Walker Class

У меня есть небольшая проблема с Walker Class'ом в wordpressМой walker:

200