Pregunta

Si bien la especificación C# incluye un preprocesador y directivas básicas (#define, #if, etc.), el lenguaje no tiene el mismo preprocesador flexible que se encuentra en lenguajes como C/C++.Creo que la falta de un preprocesador tan flexible fue una decisión de diseño tomada por Anders Hejlsberg (aunque, desafortunadamente, no puedo encontrar referencias a esto ahora).Por experiencia, esta es ciertamente una buena decisión, ya que se crearon algunas macros realmente terribles que no se podían mantener cuando estaba haciendo mucho C/C++.

Dicho esto, hay una serie de escenarios en los que podría encontrar útil un preprocesador un poco más flexible.Código como el siguiente podría mejorarse mediante algunas directivas simples de preprocesador:

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

¿Sería una buena idea escribir un preprocesador para manejar casos extremadamente simples como este?Steve McConnell escribió en Código completo (p208):

Escribe tu propio preprocesador Si un lenguaje no incluye un preprocesador, es bastante fácil escribir uno...

Estoy dividido.Fue una decisión de diseño dejar un preprocesador tan flexible fuera de C#.Sin embargo, un autor al que respeto mucho menciona que puede estar bien en algunas circunstancias.

¿Debería construir un preprocesador de C#?¿Hay alguno disponible que haga las cosas simples que quiero hacer?

¿Fue útil?

Solución

Considere echar un vistazo a una solución orientada a aspectos como PostSharp, que inyecta código a posteriori en función de atributos personalizados.Es lo opuesto a un precompilador, pero puede brindarle el tipo de funcionalidad que está buscando (notificaciones de cambios de propiedad, etc.).

Otros consejos

¿Debería construir un preprocesador de C#?¿Hay alguno disponible que haga las cosas simples que quiero hacer?

Siempre puedes usar el preprocesador de C: C# es lo suficientemente parecido en cuanto a sintaxis.M4 también es una opción.

Sé que mucha gente piensa que el código corto equivale a un código elegante, pero eso no es cierto.

El ejemplo que propones está perfectamente resuelto en código, como has demostrado, ¿para qué necesitas una directiva de preprocesador?No desea "preprocesar" su código, desea que el compilador inserte algún código en sus propiedades.Es un código común pero ese no es el propósito del preprocesador.

Con tu ejemplo, ¿Dónde pones el límite?Claramente eso satisface un patrón de observador y no hay duda de que será útil, pero hay muchas cosas que serían útiles y que en realidad se hacen porque el código proporciona flexibilidad mientras que el preprocesador no lo hace.Si intenta implementar patrones comunes a través de directivas de preprocesador, terminará con un preprocesador que debe ser tan poderoso como el lenguaje mismo.Si quieres procese su código de una manera diferente usando una directiva de preprocesador pero si solo desea un fragmento de código, busque otra manera porque el preprocesador no estaba destinado a hacer eso.

Usando un preprocesador estilo C++, el código del OP podría reducirse a esta línea:

 OBSERVABLE_PROPERTY(string, MyProperty)

OBSERVABLE_PROPERTY se vería más o menos así:

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

Si tiene 100 propiedades con las que lidiar, son ~1200 líneas de código vs.~100.¿Cuál es más fácil de leer y entender?¿Cuál es más fácil de escribir?

Con C#, suponiendo que corta y pega para crear cada propiedad, son 8 pegados por propiedad, 800 en total.Con la macro, no hay que pegar nada.¿Cuál es más probable que contenga errores de codificación?Lo cual es más fácil de cambiar si tiene que agregar, p.¿Una bandera IsDirty?

Las macros no son tan útiles cuando es probable que haya variaciones personalizadas en una cantidad significativa de casos.

Como cualquier herramienta, se puede abusar de las macros e incluso pueden resultar peligrosas en las manos equivocadas.Para algunos programadores, se trata de una cuestión religiosa y los méritos de un enfoque sobre otro son irrelevantes;Si ese eres tú, deberías evitar las macros.Para aquellos de nosotros que utilizamos herramientas extremadamente nítidas de manera regular, hábil y segura, las macros pueden ofrecer no solo una ganancia de productividad inmediata durante la codificación, sino también durante la depuración y el mantenimiento.

El principal argumento en contra de crear un procesador previo para C# es la integración en Visual Studio:Se necesitarían muchos esfuerzos (si es posible) para que Intellisense y la nueva compilación en segundo plano funcionen sin problemas.

Las alternativas son utilizar un complemento de productividad de Visual Studio como ReSharper o CódigoRush.Este último tiene, hasta donde yo sé, un sistema de plantillas inigualable y viene con una excelente refactorización herramienta.

Otra cosa que podría ser útil para resolver los tipos exactos de problemas a los que te refieres es un marco AOP como PostSharp.
Luego puede utilizar atributos personalizados para agregar funciones comunes.

Para obtener el nombre del método actualmente ejecutado, puede consultar el seguimiento de la pila:

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

Cuando está en un método de conjunto de propiedades, el nombre es set_Property.

Utilizando la misma técnica, también puede consultar el archivo fuente y la información de línea/columna.

Sin embargo, no comparé esto; crear el objeto stacktrace una vez para cada conjunto de propiedades puede ser una operación que requiere demasiado tiempo.

Creo que posiblemente te estés perdiendo una parte importante del problema al implementar INotifyPropertyChanged.Su consumidor necesita una forma de determinar el nombre de la propiedad.Por esta razón, debe tener los nombres de sus propiedades definidos como constantes o cadenas estáticas de solo lectura, de esta manera el consumidor no tiene que "adivinar" los nombres de las propiedades.Si utilizara un preprocesador, ¿cómo sabría el consumidor cuál es el nombre de la cadena de la propiedad?

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

Si estuviera diseñando la próxima versión de C#, pensaría en que cada función tenga una variable local incluida automáticamente que contenga el nombre de la clase y el nombre de la función.En la mayoría de los casos, el optimizador del compilador lo eliminaría.

Aunque no estoy seguro de que haya mucha demanda para ese tipo de cosas.

@Jorge escribió:Si desea procesar su código de una manera diferente, use una directiva de preprocesador, pero si solo desea un fragmento de código, busque otra manera porque el preprocesador no estaba destinado a hacer eso.

Interesante.Realmente no considero que un preprocesador funcione necesariamente de esta manera.En el ejemplo proporcionado, estoy haciendo una sustitución de texto simple, que está en línea con la definición de preprocesador en Wikipedia.

Si este no es el uso adecuado de un preprocesador, ¿cómo deberíamos llamar un simple reemplazo de texto, que generalmente debe ocurrir antes de una compilación?

Al menos para el escenario proporcionado, existe una solución más limpia y con seguridad de tipos que construir un preprocesador:

Utilice genéricos.Al igual que:

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

Esto puede ampliarse fácilmente para devolver un PropertyInfo en cambio, le permite obtener muchas más cosas además del nombre de la propiedad.

Ya que es un Extension method, puede utilizar este método en prácticamente todos los objetos.


Además, esto es seguro para escribir.
No puedo enfatizar eso lo suficiente.

(Sé que es una vieja pregunta, pero descubrí que carece de una solución práctica).

Si está listo para deshacerse de C#, es posible que desee consultar el Abucheo lenguaje que tiene una increíble flexibilidad macro apoyo a través de AST Manipulaciones (árbol de sintaxis abstracta).Realmente es genial si puedes deshacerte del lenguaje C#.

Para obtener más información sobre Boo, consulte estas preguntas relacionadas:

Licenciado bajo: CC-BY-SA con atribución
No afiliado a StackOverflow
scroll top