C #: написание MSIL для добавления директивы препроцессора - PullRequest
8 голосов
/ 30 января 2010

Возможно ли в C # написать код MSIL, который добавит в код директиву препроцессора, например, #warning, если будет выполнено определенное условие? Или, может быть, это можно сделать с помощью отражения, я не знаю.

Я пытаюсь написать собственный атрибут, который при неправильном применении к методу или свойству класса выдаст предупреждение компилятора. Использование существующего атрибута Obsolete не будет работать, потому что тогда просто использование моего пользовательского атрибута вызовет предупреждение, а я этого не хочу. Я хочу, чтобы конструктор пользовательских атрибутов проверил условие, а если это условие истинно, то вызовет предупреждение о компиляции.

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

Ответы [ 7 ]

6 голосов
/ 30 января 2010

Я видел этот вопрос из вашей предыдущей темы. Неправильно процитировать великого Джейми Завински: «Некоторые люди, сталкиваясь с проблемой, думают:« Я знаю, я буду использовать атрибут. «Теперь у них две проблемы».

Атрибут - это просто внеполосные данные, скомпилированные в метаданные сборки. Он не может влиять на выполнение программы или поведение инструмента, если только программа или инструмент явно не запрограммированы на распознавание определенного атрибута. Это нужно сделать с помощью Reflection.

Что вам нужно сделать, это написать свой собственный инструмент. Он должен выполняться после сборки сборки, используя шаг Post-Build для проекта. Он должен загрузить сборку и использовать Reflection для итерации типов в сборке. Для каждого типа выполните итерации методов с Type.GetMethods () и используйте MethodInfo.GetCustomAttributes (), чтобы обнаружить и создать атрибут, который мог быть запрограммирован.

Вы можете использовать Type.GetInterfaces (), чтобы узнать, какие интерфейсы реализованы типом. Теперь вы можете жаловаться, когда видите, что существует метод, который реализует метод интерфейса, но отсутствует атрибут, который говорит об этом. И ваша конечная цель: вы можете жаловаться, когда видите метод с атрибутом, который говорит, что он реализует метод интерфейса, но тип больше не наследует его.

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

2 голосов
/ 07 февраля 2010

Вы когда-нибудь слышали о Бу ? У него есть интересные способы, которыми вы можете подключиться к конвейеру компилятора. Одна такая функция называется синтаксические атрибуты , которые являются атрибутами, которые реализуют интерфейс, вызываемый компилятором, чтобы они могли участвовать в генерации кода.

class Person:
  [getter(FirstName)]
  _fname as string

  [getter(LastName)]
  _lname as string

  def constructor([required] fname, [required] lname):
    _fname = fname
    _lname = lname

Атрибуты в этом коде будут генерировать публичные методы получения для полей и нулевые проверки для параметров конструктора. Все это закончится в скомпилированной сборке.

Я всегда хотел, чтобы этот вид расширяемости был частью компилятора C #. Может быть, это когда-нибудь. До этого вы можете использовать посткомпилятор, например CciSharp . CCiSharp перезапишет CIL на основе специальных атрибутов в сборке, точно так же, как с синтаксическими атрибутами Boo.

Учитывая этот код:

class Foo {
  [Lazy]
  public int Value { 
    get { return Environment.Ticks; } 
  }
}

CCiSharp изменит код на основе LazyAttribute на этот:

class Foo {
  int Value$Value; // compiler generated
  int Value$Initialized;
  int GetValueUncached() { 
    return Environment.Ticks;
  }
  public int Value  {
    get {
      if(!this.Value$Initialized) {
        this.Value$Value = this.GetValueUncached();
        this.Value$Initialized = true;
      }
      return this.Value$Value;
    }
}

CCiSharp основан на проекте Общая инфраструктура компилятора *1025*, который используется для реализации посткомпилятора кода контрактов в готовящейся .NET Framework 4.0.

Так вот как вы можете изменить сгенерированный CIL.

Но директива #warning не имеет представления CIL, это только директива компилятора. Чтобы добавить эту директиву, необходимо изменить не сгенерированный CIL, а сам код C #. Для этого вам нужно реализовать C # парсер. Я думаю, что лучшим вариантом, как указано в других ответах, является создание события после сборки, которое отразится на сгенерированной сборке и выдаст нужное предупреждение.

2 голосов
/ 30 января 2010

Короче, нет. Директивы предварительной обработки не имеют представления IL, поскольку они существуют только как метаданные, используемые при компиляции исходного файла.

То, что вы делаете, может быть лучше, чем пользовательское правило FxCop .

2 голосов
/ 30 января 2010

Компилятор хранит две вещи для пользовательских атрибутов:

  • Конструктор атрибута для вызова
  • Данные для каждого параметра для передачи в конструктор

Конструктор вызывается только тогда, когда приложение работает, и кто-то вызывает GetCustomAttributes для вашей сборки, типа, MethodInfo, ParameterInfo и т. Д.

У вас есть другие варианты для рассмотрения:

  • Напишите пользовательскую задачу MSBuild, которая запускается после этапа компиляции, загружает скомпилированную сборку и проверяет использование атрибутов приложения.
  • Используйте атрибут AttributeUsage, чтобы указать элементы кода, к которым можно применить атрибут.
  • Отложить проверку атрибута до времени выполнения.
1 голос
/ 30 января 2010

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

Надеюсь, это поможет, С наилучшими пожеланиями, Том.

1 голос
/ 30 января 2010

Возможно, вы могли бы сделать это с помощью задачи после сборки , которая отражает ваш скомпилированный код и проверяет условие. У меня нет опыта создания задач MSBuild, но это место, с которого можно начинать.

Еще одно предложение после выпуска .NET 4.0 - использовать Кодовые контракты для определения требований к параметрам конструктора атрибута. Это будет обнаружено во время компиляции.

0 голосов
/ 30 января 2010

Я подозреваю, что ответ будет "нет", вы не можете, потому что директива #warning - это CSC (то есть вы указываете компилятору действовать определенным образом). Очевидно, что при написании необработанного MISL CSC не смешивается, поэтому нет компилятора, который бы указывал что-то делать.

В основном директива (обозначаемая как '#warning' - это инструкция для CSC вести себя определенным образом при определенных условиях.

...