Может ли ‘этот’ аргумент метода экземпляра быть нетипизированным (т.Е. System.Объект)?

#c# #cil #reflection.emit

#c# #cil #отражение.излучение

Вопрос:

Я использую System.Reflection.Emit ‘s TypeBuilder для генерации множества пользовательских.СЕТЕВЫЕ классы с методами экземпляра. Например:

 public class EmittedClass
{
    public bool TryGetName(out string value)
    {
        ...
    }

    public bool TryGetAge(out int value)
    {
        ...
    }
}
  

Все методы следуют одной и той же сигнатуре, которая может быть описана универсальным делегатом:

 public delegate bool TryGetter<T>(out T value);
  

Конечно, я хотел бы иметь возможность явно указывать целевой экземпляр на сайте вызова, например:

 var instance = InstanceFactory.CreateInstance();
var tryGetName = InstanceFactory.CreateTryGetter<string>("Name");
string name;
if (tryGetName(instance, out name)) // Problem here.
{
    ...
}
  

Чтобы это сработало, мне нужно преобразовать делегат в так называемый открытый делегат:

 public delegate bool TryGetter<T>(object instance, out T value);
  

Поскольку у меня нет типа целевого экземпляра во время компиляции, мне нужно передать его как System.Object . Однако это прерывается во время выполнения, поскольку метод экземпляра ожидает, что его ‘this’ будет типа объявляющего класса. Ой.

Решение, которое я использую в настоящее время, заключается в создании промежуточного лямбда-выражения, которое принимает входной объект, выполняет приведение среды выполнения к целевому типу, а затем переходит к вызову целевого метода. Это работает, но я чувствую себя неловко из-за того, что этот клудж находится посередине.

Вопрос в следующем: могу ли я каким-то образом изменить мои исходящие методы, чтобы их this аргументы принимали любые System.Object и при этом сохраняли проверяемость моего исходящего кода?

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

Просто интересно, может ли кто-нибудь более осведомленный о CIL дать мне совет по этому вопросу. Спасибо!

ОБНОВЛЕНИЕ: проблема, которую я пытаюсь решить

Классы имеют свойства, которые обычно поддерживаются полями. Это нормально, если ожидается, что экземпляры указанных классов будут поддерживать только одно состояние одновременно. Под состоянием я подразумеваю комбинацию значений в полях экземпляра в данный момент времени.

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

Основная идея моего подхода заключается в создании зеркального отображения бизнес-класса с использованием System.Reflection.Emit и в том, чтобы бизнес-класс поддерживал набор этих экземпляров, каждый из которых соответствует заданному состоянию. Тогда получатели и установщики бизнес-класса, естественно, должны быть подключены к соответствующим методам в экземпляре состояния. Требуется гораздо больше деталей, но это основная идея.

Я надеюсь, что объяснение поможет понять причину, по которой я задаю этот вопрос. Я понимаю, что многим это может показаться чрезмерной разработкой, но другие альтернативы, которые не включают System.Reflection.Emit , просто слишком медленные и ресурсоемкие. Я не могу этого допустить.

Комментарии:

1. Я думаю, вы подошли к чему-то очень интересному. Однако вы задаете вопрос наоборот. Вместо того, чтобы спрашивать, может ли быть принято плохое решение, запрос хорошего решения привел бы к более полезным ответам, ИМХО

2. Точка снята. Я не объяснил проблему, я пытаюсь решить. Я обновлю свой вопрос.

3. В ответ на вашу правку: в чем ваша реальная бизнес-проблема? Зачем вам нужно, чтобы объект имел два состояния одновременно — почему не два разных объекта?

4. @Dan: Ну, это два разных объекта, не так ли? Один из них является бизнес-объектом, а другой — объектом состояния. Но я знаю, что вы имеете в виду. 🙂 Одна из причин заключается в том, что бизнес-объект также обладает другими свойствами (не говоря уже о множестве методов), которые не имеют ничего общего с управлением состоянием. Для этого простое повторное использование того же класса для состояния не является вариантом. Вторая часть причины заключается в том, что потребитель бизнес-объекта не должен быть обременен несколькими объектами. Для него это действительно всего лишь один объект. Заключительная часть причины заключается в том, чтобы избежать ручного ввода кода котельной плиты.

5. @aoven: Вы сказали в своей правке «Мое бизнес-требование заключается в реализации хранилища, альтернативного обычным полям, которое позволит экземпляру одного класса иметь более одного состояния одновременно». Это не бизнес-требование, это техническое решение бизнес-требования. Я не могу представить причину предпочесть стопки System.Reflection.Emit кода любому количеству шаблонного кода — вы уверены, что это правильный ответ?

Ответ №1:

Вам не нужно представлять тип ‘this’ в подписи делегата.

Для этого дается определение делегата следующим образом:

 public delegate bool TryGetter<T>(out T value);
  

Любая переменная вида:

 TryGetter<T> x;
  

Может содержать либо:

  1. Метод экземпляра типа R с сигнатурой bool Foo(out T value) вместе с экземпляром объекта типа R.
  2. Статический метод с сигнатурой static bool Foo(out T value)
  3. Статический метод с сигнатурой static bool Foo(R object, out T value) , предоставляющий экземпляр объекта типа R.

Третья форма называется обработкой делегирования и позволяет статическому методу с N 1 аргументами вести себя так, как если бы это был метод экземпляра с N аргументами (однако может быть обработан только первый аргумент).

Итак, интерфейс, который вы хотите, это:

 var instance = InstanceFactory.CreateInstance();
var tryGetName = InstanceFactory.CreateTryGetter<string>(instance,"Name");
  

Затем вы можете сделать: tryGetName() чтобы вернуть значение.

Вы, вероятно, хотели бы перейти к варианту # 3, где вы сгенерировали DynamicMethod с подписью bool TryGetWhatEver(TheTypeOfInstance obj, out WhatEver x) , а затем создали TryGetter<WhatEver> .


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

Комментарии:

1. Ага. Мы определенно говорим здесь о больших кусках. 🙂

Ответ №2:

Учитывая ваши требования, разве библиотека, подобная ValueInjecter или AutoMapper, не сократила бы весь шаблонный код копирования из одного большого бизнес-объекта в разные объекты состояния? Даже если это не совсем то, что вы хотите, возможно, они могли бы послужить источником вдохновения для вашей задачи.

Комментарии:

1. Боюсь, что эти библиотеки решают другую проблему высокого уровня. Во всяком случае, я на самом деле стремлюсь к полной противоположности копированию значений между объектами: я пытаюсь сохранить их в одном месте (объект состояния) и перенаправить на них из других мест (бизнес-объект, например). В любом случае спасибо!

Ответ №3:

Отражение.Emit — это, пожалуй, излишество для создания механизма внутреннего хранения для размещения копий значений ваших свойств. Что-то столь простое, как несколько словарей, было бы достаточным способом хранения различных ‘состояний’ сопоставлений propertyname-> value.

Комментарии:

1. Конечно. Это было именно мое первоначальное решение. Я хорошо помню свои мысли в то время: «Я знаю, что словарь немного расточителен со стороны памяти. Но насколько это может быть плохо?» Оказалось, что это может быть довольно плохо. Два словаря на тип, умноженные на три типа на экземпляр бизнес-объекта, в среднем на тысячу или около того бизнес-объектов в типичной коллекции составляют значительно более 1 МБ памяти. Учитывая все другие вещи, которые тратят память (законно) в бизнес-приложении, это было совершенно неприемлемо. Плюс большая часть производительности вылетела прямо из окна из-за бокса.

Ответ №4:

Просто используйте dynamic :

 dynamic instance = InstanceFactory.CreateInstance();
var tryGetName = InstanceFactory.CreateTryGetter<string>("Name");
string name;

// Should work if “instance” is of the right type *at runtime*
if (tryGetName(instance, out name))
{
    ...
}