Получение имен локальных переменных (и параметров) во время выполнения через лямбда-выражения

Мне интересно получить имена локальных переменных (и параметров) во время выполнения в режиме рефакторинга. У меня есть следующий метод расширения:

public static string GetVariableName<T>(Expression<Func<T>> variableAccessExpression)
{
    var memberExpression = variableAccessExpression.Body as MemberExpression;
    return memberExpression.Member.Name;
}

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

static void Main(string[] args)
{
    Console.WriteLine(GetVariableName(() => args));
    // Output: "args"

    int num = 0;
    Console.WriteLine(GetVariableName(() => num));
    // Output: "num"
}

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

Является ли эта переменная продвигаемым документированным поведением, или это детали реализации могут быть изменены в других версиях структуры?

Примечание. Этот вопрос является обобщением моего прежнего при проверке аргументов.

Ответы

Ответ 1

Обновление. Это больше не проблема с С# 6, в которой введен оператор nameof для решения таких сценариев (см. MSDN).

Похоже, что ответ на мой вопрос: нет; эта функция нестандартизирована. Ситуация кажется еще более мрачной, чем первоначально предполагалось изначально; не только продвижение захваченных переменных нестандартизировано, но и вся спецификация преобразования анонимных функций в их представления дерева выражений.

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

Expression<Func<int, int, int>> add = (int x, int y) => x + y;

Следующие фрагменты взяты из С# Language Specification 4.0 (выделение добавлено во всех случаях).

Из "4.6 Деревья выражений":

Точное определение типового типа Expression<D>, а также точные правила построения дерева выражений, когда анонимная функция преобразуется в тип дерева выражений, выходят за рамки этой спецификации > и описаны в другом месте.

Из "6.5.2 Оценка анонимных преобразований функций в типы дерева выражений":

Преобразование анонимной функции в тип дерева выражений создает дерево выражений (§4.6). Точнее, оценка преобразования анонимной функции приводит к построению структуры объекта, которая представляет собой структуру самой анонимной функции. Точная структура дерева выражений, а также точный процесс его создания определяются реализацией.

Третий пример в "6.5.3 примере реализации" демонстрирует преобразование анонимной функции, которая фиксирует локальную переменную, и подтверждает указанную в моем вопросе рекламу переменной:

Время жизни локальной переменной теперь должно быть увеличено до, по крайней мере, времени жизни анонимного делегата функции. Это может быть достигнуто путем "подъема" локальной переменной в поле сгенерированного компилятором класса. Создание локальной переменной (§7.15.5.2) затем соответствует созданию экземпляра сгенерированного класса компилятора, а доступ к локальной переменной соответствует доступу к полю в экземпляре сгенерированного компилятором класса.

Это дополнительно подтверждается в конце раздела:

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

Однако в начале раздела есть отказ от ответственности:

Реализация, описанная здесь, основана на тех же принципах, которые используются компилятором Microsoft С#, но это никоим образом не утвержденная реализация, и она не единственная возможная. Он лишь кратко упоминает преобразования в деревья выражений, так как их точная семантика выходит за рамки этой спецификации.

P.S. Eric Lippert подтверждает в этом комментарии, что спецификации дерева выражений никогда не отправлялись. Существует Expression Trees v2 Spec в документации DLR на CodePlex, но его область видимости не охватывает преобразование анонимных функций в деревья выражений в С#.

Ответ 2

Это поведение, на которое вы должны не полагаться.

Взгляните на Нарушение выражений лямбда С# или яркости синтаксиса?

Теперь прочитайте комментарии Эрика Липперта, который находится в команде разработчиков С#. К ним относятся:

Я просто спросил Андерса (и остальную часть команды дизайнеров), что они думал. Допустим, что результаты не будут печататься в семейная газета

и

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

Из этих утверждений я бы сказал, что это не станет документированным или поддерживаемым поведением.

Ответ 3

AFAIK, это деталь реализации.

Однако, я думаю, вы можете поспорить, что это не изменится.

Я только что протестировал в VS2012 RC, и он работает так, как ожидалось, поэтому вы будете в безопасности на пару лет как минимум.