Препроцессор C # - PullRequest
       40

Препроцессор C #

20 голосов
/ 01 сентября 2008

Хотя спецификация C # включает препроцессор и базовые директивы (#define, #if и т. Д.), Язык не имеет такого же гибкого препроцессора, как в таких языках, как C / C ++. Я считаю, что отсутствие такого гибкого препроцессора было дизайнерским решением, принятым Андерсом Хейлсбергом (хотя, к сожалению, сейчас я не могу найти ссылку на это). Исходя из опыта, это, безусловно, хорошее решение, так как были некоторые действительно ужасные необслуживаемые макросы, созданные еще тогда, когда я много занимался C / C ++.

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

public string MyProperty
{
  get { return _myProperty; }
  set
  {
    if (value != _myProperty)
    {
      _myProperty = value;
      NotifyPropertyChanged("MyProperty");
      // This line above could be improved by replacing the literal string with
      // a pre-processor directive like "#Property", which could be translated
      // to the string value "MyProperty" This new notify call would be as follows:
      // NotifyPropertyChanged(#Property);
    }
  }
}

Будет ли хорошей идеей написать препроцессор для обработки таких простых случаев, как этот? Стив Макконнелл написал в Код завершен (p208):

Напишите свой собственный препроцессор Если язык не включает препроцессор, его довольно легко написать ...

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

Должен ли я создать препроцессор C #? Есть ли один, который делает простые вещи, которые я хочу сделать?

Ответы [ 11 ]

11 голосов
/ 01 сентября 2008

Попробуйте взглянуть на аспектно-ориентированное решение, такое как PostSharp , которое вводит код после факта, основанного на пользовательских атрибутах. Это противоположность прекомпилятору, но может дать вам ту функциональность, которую вы ищете (уведомления PropertyChanged и т. Д.).

6 голосов
/ 01 сентября 2008

Должен ли я создать препроцессор C #? Есть ли один, который делает простые вещи, которые я хочу сделать?

Вы всегда можете использовать препроцессор C - C # достаточно близок по синтаксису. M4 также вариант.

4 голосов
/ 01 сентября 2008

Я знаю, что многие думают, что короткий код - это элегантный код, но это не так.

Пример, который вы предлагаете, прекрасно решен в коде, как вы показали, зачем вам директива препроцессора? Вы не хотите «предварительно обрабатывать» свой код, вы хотите, чтобы компилятор вставил какой-то код для вас в ваши свойства. Это обычный код, но это не цель препроцессора.

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

3 голосов
/ 30 сентября 2013

Используя препроцессор в стиле C ++, код OP можно сократить до одной строки:

 OBSERVABLE_PROPERTY(string, MyProperty)

OBSERVABLE_PROPERTY будет выглядеть примерно так:

#define OBSERVABLE_PROPERTY(propType, propName) \
private propType _##propName; \
public propType propName \
{ \
  get { return _##propName; } \
  set \
  { \
    if (value != _##propName) \
    { \
      _##propName = value; \
      NotifyPropertyChanged(#propName); \
    } \
  } \
}

Если у вас есть 100 свойств, то это ~ 1200 строк кода против ~ 100. Что легче читать и понимать? Что легче написать?

В C # предполагается, что вы создаете каждое свойство с помощью вырезания и вставки, то есть 8 вставок на свойство, всего 800. С макросом, без склеивания вообще. Что может содержать ошибки кодирования? Что легче изменить, если вам нужно добавить, например, флаг IsDirty?

Макросы не так полезны, когда в значительном числе случаев могут быть нестандартные варианты.

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

3 голосов
/ 01 сентября 2008

Основным аргументом в пользу создания препроцессора для C # является интеграция в Visual Studio: потребовалось бы много усилий (если это вообще возможно), чтобы получить intellisense и новый фоновый компилятор для бесперебойной работы.

Альтернативы - использовать плагин производительности Visual Studio, например ReSharper или CodeRush . Последний, насколько мне известно, имеет непревзойденную систему шаблонов и поставляется с отличным инструментом рефакторинга .

Еще одна вещь, которая может помочь в решении конкретных типов проблем, на которые вы ссылаетесь, - это AOP-инфраструктура, такая как PostSharp .
Затем вы можете использовать пользовательские атрибуты для добавления общих функций.

1 голос
/ 27 августа 2009

Я думаю, что вы, возможно, упускаете одну важную часть проблемы при реализации INotifyPropertyChanged. Вашему потребителю нужен способ определения названия недвижимости. По этой причине вы должны иметь имена своих свойств, определенные как константы или статические строки только для чтения, чтобы потребителю не приходилось «угадывать» имена свойств. Если вы использовали препроцессор, как бы потребитель узнал, что такое строковое имя свойства?

public static string MyPropertyPropertyName
public string MyProperty {
    get { return _myProperty; }
    set {
        if (!String.Equals(value, _myProperty)) {
            _myProperty = value;
            NotifyPropertyChanged(MyPropertyPropertyName);
        }
    }
}

// in the consumer.
private void MyPropertyChangedHandler(object sender,
                                      PropertyChangedEventArgs args) {
    switch (e.PropertyName) {
        case MyClass.MyPropertyPropertyName:
            // Handle property change.
            break;
    }
}
1 голос
/ 01 сентября 2008

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

public static string GetNameOfCurrentMethod()
{
    // Skip 1 frame (this method call)
    var trace = new System.Diagnostics.StackTrace( 1 );
    var frame = trace.GetFrame( 0 );
    return frame.GetMethod().Name;
}

Когда вы используете метод набора свойств, имя будет set_Property.

Используя ту же технику, вы также можете запросить информацию об исходном файле и строке / столбце.

Однако я не тестировал это, создание объекта трассировки стека один раз для каждого набора свойств может быть слишком длительной операцией.

0 голосов
/ 25 сентября 2013

По крайней мере для предоставленного сценария, есть более чистое, безопасное для типов решение, чем создание препроцессора:

Используйте дженерики. Вот так:

public static class ObjectExtensions 
{
    public static string PropertyName<TModel, TProperty>( this TModel @this, Expression<Func<TModel, TProperty>> expr )
    {
        Type source = typeof(TModel);
        MemberExpression member = expr.Body as MemberExpression;

        if (member == null)
            throw new ArgumentException(String.Format(
                "Expression '{0}' refers to a method, not a property",
                expr.ToString( )));

        PropertyInfo property = member.Member as PropertyInfo;

        if (property == null)
            throw new ArgumentException(String.Format(
                "Expression '{0}' refers to a field, not a property",
                expr.ToString( )));

        if (source != property.ReflectedType ||
            !source.IsSubclassOf(property.ReflectedType) ||
            !property.ReflectedType.IsAssignableFrom(source))
            throw new ArgumentException(String.Format(
                "Expression '{0}' refers to a property that is not a member of type '{1}'.",
                expr.ToString( ),
                source));

        return property.Name;
    }
}

Это можно легко расширить, чтобы вместо него вернуть PropertyInfo, что позволит вам получить гораздо больше информации, чем просто имя свойства.

Поскольку это Extension method, вы можете использовать этот метод практически для каждого объекта.


Кроме того, это безопасно для типов.
Не могу не подчеркнуть этого достаточно.

(Я знаю, что это старый вопрос, но я нашел, что в нем нет практического решения.)

0 голосов
/ 12 июля 2010

Если вы готовы отказаться от C #, вы можете попробовать язык Boo , который обладает невероятно гибкой поддержкой macro через AST (Абстрактное синтаксическое дерево) манипуляции. Это действительно здорово, если вы можете отказаться от языка C #.

Для получения дополнительной информации о Boo см. Следующие вопросы:

0 голосов
/ 01 сентября 2008

@ Jorge писал: Если вы хотите обрабатывать свой код другим способом, используйте директиву препроцессора, но если вы просто хотите фрагмент кода, найдите другой способ, потому что препроцессор не предназначен для этого.

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

Если это не правильное использование препроцессора, что мы должны называть простой заменой текста, которая обычно должна выполняться перед компиляцией?

...