Comments 15
Для нарушения инкапсуляции инструмент годный. А вот насчёт рефлексии, мне всегда казалось, что она существует для того, чтобы пользоваться обьектами произвольных типов, которые разработчику вообще могут быть неизвестны. Ну как например я сериализую вызов функции и параметры, сохраняю куда угодно, а потом через рефлексию этот вызов произвожу, и для этого интерфейса мне не важно какого типа обьект, какая функция вызывается, что возвращает, я могу просто сделать сериализацию и вызов произвольной процедуры в вакууме.
Согласен, целью рефлексии, разумеется, не является нарушение инкапсуляции. Однако это не отменяет того факта, что с ее помощью (при желании), нарушение инкапсуляции легче некуда.
Серьезный результат. Товарищ Warren указывает Get доступ к свойству в 0.21ns, когда у вас 0.05ns на доступ к полю.

Насколько реально отстрелить себе ноги с таким подходом? Точнее, вы пробовали применять его в продакшне?

Не пробовал и, если честно, не хотелось бы.
Я думал о возможностях использования, но возможность отстрела ноги слишком велика. Представляет интерес скорее с научной точки зрения.

Совершенно согласен. Если нужны такие танцы с бубном, то очевидно, что выбран не тот язык (платформа) для реализации задачи. Т.е. все это интересно, но исключительно с точки зрения лучшего понимания внутренностей, а не для практического применения. Ну или костыль для легаси. Там и не такое встречается.

Если я увижу что-то подобное в продакшн коде своих разработчиков — голову отверну не глядя.
Если выстрел в голову в упор из дробовика не задевает мозг — это либо большое везение, либо мастерство. Первое вероятнее.

Бенчмарк стоит дополнить возможными способами оптимизции:


  • Сохранить MethodInfo, FieldInfo вместо вычисления каждый раз
  • Преобразовать в делегат
  • Использовать ExpressionTree, ILGenerator

Последние варианты проигрывают прямому вызову совсем немного.

Разрядка для мозгов: Что будет выведено при вызове виртуального свойства CustomClass?

А можно для не просвящённых почему так?
Я понимаю, какой был бы результат, если бы свойство было не виртуальным, но почему возвращается null для виртуального я не догоняю.


Мне бы хотелось получить что-нибудь вроде MethodNotFoundException.

На самом деле вопрос действительно сложный.
Весь этот пример основан лишь на смещениях. Например, в смещении метода CompareTo я уверен, он совпадает с моим CompareTo. Однако свойство, когда идет первым, проецируется на другой метод. В моем случае это Equals. Попробуйте сделать вместо данного свойства аналог метода Equals. А далее попробуйте передать такую-же строку («4564» в моем случае), или другую. Результаты будут ожидаемыми для метода Equals (true и false соотвественно).
Причина же по которой возвращаемое значение становится null (строго говоря, дефолтным, для int будет 0) мною не разгадана. Если вам интересно, попробуйте подебажить в dnSpy, там можно своими глазами увидеть, в какой метод переходит выполнение. Порядок методов в таблице методов не тривиален. Есть правила, по которым располагаются методы, но я предпочитаю видеть точно.
Могу высказать текущую гипотезу. Во-первых: при проецировании на Equals, параметр внуть метода строки передается null, скорее всего это связано с тем, что данный параметр передается через регистры, и значение в регистре при вызове метода соответсвет null. Второе — при этом метод возвращает false, то бишь нули (память). Которые интерпритируются как null в случае ссылочных типов, как 0 в случае int и тд.
Это звучит довольно дико для шарпа. И я не утвержаю, а лишь предполагаю. Возможно, это послужит толчком для дальнейших исследований (моих или ваших).

Хм, если честно у меня более прозаическая догадка.
Игрался с таким кодом


Заголовок спойлера
public class CustomClass
{
    public override string ToString()
    {
        return "CUSTOM";
    }

    public virtual object SomeVirtualMethod()
    {
        return "SomeVirtualMethod";
    }

    public object SomeMethod()
    {
        return "SomeMethod";
    }
}

[StructLayout(LayoutKind.Explicit)]
public class CustomStructWithLayout
{
    [FieldOffset(0)]
    public string Str;

    [FieldOffset(0)]
    public CustomClass SomeInstance;
}

class Program
{
    static void Main(string[] args)
    {
        CustomStructWithLayout instance = new CustomStructWithLayout();
        instance.SomeInstance = new CustomClass();
        instance.Str = "4564";
        Console.WriteLine(instance.SomeInstance.GetType()); //System.String
        Console.WriteLine(instance.SomeInstance.ToString()); //4564     

        Console.WriteLine(instance.SomeInstance.SomeMethod()); // SomeMethod
        Console.WriteLine(instance.SomeInstance.SomeVirtualMethod()); // null       
    }
}

И как мне кажется, для виртуального метода CLR генерит метод-заглушку, возвращающий дефолтное значение.


Поскольку, насколько я понимаю, для вызова виртуального метода CLR необходимо действительно найти этот метод в иерархии типа, и так как метода SomeVirtualMethod нет в иерархии типа string, то и возвращается эта заглушка.

Виртуальные методы вызываются по смещению в таблице методов. На этом и основана вся эта статья.
И благодаря этому при вызове метода CompareTo на CustomClass, вызывается метод строки. Это же и объясняет поведение при замене виртуального свойства на клон Equals. Заглушка действительно генерируется(для всех методов), но ради последующей компиляции JITом(имеет единсвеннную инструкцию на тригеринг JITа), которая потом затирается и изменяется на jmp в нужное место памяти, где расположен скомпилированный метод.

Еще, как вариант (Возможно, всем известно, но мало ли):

Допустим есть метод
Код string.CreateTrimmedString()
[SecurityCritical]
private string CreateTrimmedString(int start, int end)
{
      int length = end - start + 1;
      if (length == this.Length)
        return this;
      if (length == 0)
        return string.Empty;
      return this.InternalSubString(start, length);
}



Чтобы вызвать этот метод, можно написать:
var method = typeof(string).GetMethod("CreateTrimmedString", BindingFlags.Instance | BindingFlags.NonPublic);
var text = "123";
method.Invoke(text, new object[] { 1, 1 })
// "2"


Можно протестировать в С# Interactive
Фух, я уж думал, я опозорился начав писать коменты, не прочитав статью.
Да, глупо получилось, посмотрел код под спойлером только после отправки комментария. А отменить было уже никак.
Only those users with full accounts are able to leave comments. Log in, please.