Подтвердить что ты не робот

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

В то время как спецификация С# содержит предустановочные и базовые директивы (#define, #if и т.д.), язык не имеет такого же гибкого предварительного процессора, что и на таких языках, как 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);
    }
  }
}

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

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

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

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

4b9b3361

Ответ 1

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

Ответ 2

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

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

Ответ 3

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

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

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

Ответ 4

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

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

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

Ответ 5

Используя препроцессор в стиле С++, код 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. Что легче читать и понимать? Что проще написать?

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

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

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

Ответ 6

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

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.

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

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

Ответ 7

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

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;
    }
}

Ответ 8

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

Я не уверен, что существует большая потребность в таких вещах.

Ответ 9

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

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

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

Ответ 10

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

Используйте generics. Например:

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;
    }
}

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

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


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

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

Ответ 11

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

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