Вопрос

Хотя спецификация 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);
    }
  }
}

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

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

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

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

Это было полезно?

Решение

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

Другие советы

  

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

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

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

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

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

Используя препроцессор в стиле 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?

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

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

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

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

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

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

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.

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

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

Я думаю, что вы, возможно, упускаете одну важную часть проблемы при реализации 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;
    }
}

Если бы я разрабатывал следующую версию C #, я бы подумал о том, чтобы каждая функция имела автоматически включенную локальную переменную, содержащую имя класса и имя функции. В большинстве случаев оптимизатор компилятора уберет его.

Я не уверен, что есть большой спрос на подобные вещи.

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

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

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

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

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

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 , что позволит вам получить гораздо больше информации, чем просто имя свойства.

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


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

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

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

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

Лицензировано под: CC-BY-SA с атрибуция
Не связан с StackOverflow
scroll top