2008-08-31 31 views
19

Si bien la especificación C# incluye un preprocesador y directivas básicas (#define, #if, etc.), el idioma 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 hecha por Anders Hejlsberg (aunque, desafortunadamente, no puedo encontrar una referencia a esto ahora). Por experiencia, esta es sin duda una buena decisión, ya que hubo algunas macros realmente imposibles de mantener creadas cuando estaba haciendo mucho C/C++.Preprocesador C#

Dicho esto, hay una serie de situaciones en las que podría encontrar un preprocesador un poco más flexible para ser útil. Código como el siguiente podría mejorarse algunas directivas pre-procesador simples:

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 para escribir un pre-procesador para manejar casos extremadamente simples como esto? Steve McConnell escribió en Code Complete (P208):

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

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

¿Debo crear un preprocesador C#? ¿Hay uno disponible que haga las cosas simples que quiero hacer?

+0

¿Has encontrado una buena solución para esto? La repetición de indicadores y accesadores "IsDirty" por todos lados es una mierda. –

+0

No encontré una solución perfecta, pero tuvimos un gran éxito con IL Weaving a través de [NotifyPropertyWeaver] (https://github.com/SimonCropp/NotifyPropertyWeaver). –

+0

Por lo que vale, he escrito un preprocesador C# que uso para varios propósitos. Hace poco respondí otra pregunta en SO al publicar un preprocesador de C# simple de "prueba de concepto": http: // stackoverflow.com/a/18158212/253938 – RenniePet

Respuesta

11

Considere la posibilidad de echarle un vistazo a una solución orientada a aspectos como PostSharp, que inyecta código después del hecho en función de los atributos personalizados. Es lo opuesto a un precompilador, pero puede darle el tipo de funcionalidad que está buscando (notificaciones PropertyChanged, etc.).

6

¿Debo crear un preprocesador C#? ¿Hay uno disponible que haga las cosas simples que quiero hacer?

Siempre puede usar el preprocesador C - C# está lo suficientemente cerca, sintaxis. M4 también es una opción.

0

Si estuviera diseñando la próxima versión de C#, pensaría en cada función que tiene una variable local incluida automáticamente que contiene 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.

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

4

Sé que mucha gente piensa que el código corto es igual al código elegante, pero eso no es cierto.

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

Con su ejemplo, ¿dónde pone el límite? Claramente, eso satisface un patrón de observador y no hay duda de que será útil, pero hay muchas cosas que podrían ser útiles porque el código proporciona flexibilidad donde no lo hace el preprocesador. Si intenta implementar patrones comunes mediante directivas de preprocesador, terminará con un preprocesador que debe ser tan poderoso como el idioma en sí.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 otro método, ya que el preprocesador no estaba destinado a hacer eso.

+0

Deseo que más personas piensen así en su código C++. Las macros complejas perjudican el mantenimiento del código y, con frecuencia, no proporcionan ningún beneficio de rendimiento. – Sqeaky

+3

Acepto que las directivas de preprocesador pueden hacer que el código sea más difícil de mantener, pero creo que tener cantidades gigantescas de código duplicado en los casos en que la abstracción no es posible es peor. –

0

@Jorge escribió: Si desea procesar el código de una manera diferente el uso de una directiva de preprocesador pero si lo que desea es un fragmento de código a continuación, encontrar otra manera debido a que el preprocesador no estaba destinado a hacer eso.

Interesante. Realmente no considero que un preprocesador necesariamente funcione 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 un preprocesador en Wikipedia.

Si este no es el uso correcto de un preprocesador, ¿qué deberíamos llamar una sustitución de texto simple, que generalmente debe producirse antes de una compilación?

+0

Y para eso deberías usar una constante de cadena, ¿alguna razón para no hacerlo? Además, ¿cuál sería el tipo de su # propiedad? ¿Qué sucede si defino #property como double? ¿Por qué querría sustituir una constante por una directiva de preconcesador? –

+0

Dado mi ejemplo en la pregunta, tener que mantener una constante que represente el nombre de la propiedad desafortunadamente no guardará ningún mantenimiento. De acuerdo, si tuviera que reutilizar la cuerda en cualquier otra parte de la clase, ciertamente usaría una constante. –

+0

(cont) Lo que podría ser interesante es tener la sintaxis #Property (por defecto a la propiedad actual), y la sintaxis # Property.MyProperty - una construcción refactorable de la herramienta, que produciría un error de tiempo de compilación si no se mapea a una propiedad real y evitar la sobrecarga de tiempo de ejecución de la reflexión. –

3

El principal argumento a favor de la construcción de un pre-procesador para C# es la integración en Visual Studio: se necesitarían muchos esfuerzos (si es posible) para obtener intellisense y la nueva compilación de fondo para trabajar sin problemas.

Las alternativas son usar un plugin de productividad de Visual Studio como ReSharper o CodeRush. Este último tiene -hasta lo que yo sé- un sistema de plantillas sin igual y viene con una excelente herramienta refactoring.

Otra cosa que podría ser útil para resolver los tipos exactos de problemas a los que se refiere es un marco de AOP como PostSharp.
A continuación, puede usar atributos personalizados para agregar funcionalidad común.

+0

La integración de un preprocesador C# en Visual Studio no es gran cosa. En cuanto a proporcionar intellisense, eso puede o no ser relevante, dependiendo de lo que el preprocesador agregue al idioma. Aquí hay un par de enlaces: http://stackoverflow.com/a/18158212/253938 y http://stackoverflow.com/a/12163384/253938 – RenniePet

1

Para obtener el nombre del método actualmente ejecutada, se puede ver en el seguimiento de 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ás en un método conjunto de propiedades, el nombre es set_property.

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

Sin embargo, no hice una evaluación comparativa de esto, crear el objeto stacktrace una vez para cada conjunto de propiedades podría ser una operación que consume demasiado tiempo.

+1

Tenga cuidado con el uso de System.Diagnostics.StackTrace(). Sé que he leído que no es una fuente confiable de información, especialmente una vez que viajas por la pila de llamadas (probablemente leer en "The Old New Thing"). La convención para llamar a los definidores de propiedades set_Property también es una cosa interna de .Net y, por lo tanto, está sujeta a cambios. La forma más confiable de referirse de manera segura a un afaik de propiedad es a través de una expresión lambda. De lo contrario, use soluciones orientadas a aspectos como se sugiere aquí. –

+1

+1 para expresiones lambda, las cosas mecanografiadas son agradables! –

1

Creo que es posible que te falte 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 sus nombres de propiedad definidos como constantes o cadenas de solo lectura estáticas, de esta manera el consumidor no tiene que 'adivinar' los nombres de las propiedades. Si utilizó 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; 
    } 
} 
-1

Si usted está listo para deshacerse de C# es posible que desee comprobar hacia fuera el idioma Boo que tiene macro apoyo increíblemente flexible a través AST manipulaciones de sintaxis abstracta (Árbol). Realmente es genial si puedes abandonar el lenguaje C#.

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

+0

Booh a los downvoters que no dejaron ningún comentario. De Verdad? Eso es pobre SO habilidades sociales. (¿Qué puedo esperar, sin embargo, esta es una multitud de geeks súper). –

0

Al menos para el escenario proporcionado, hay una solución más limpia y segura para el tipo que construir un preprocesador:

Use genéricos. De este modo:

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 se puede ampliar fácilmente para devolver un PropertyInfo lugar, que le permite obtener de forma más cosas que sólo el nombre de la propiedad.

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


Además, esto es tipo seguro.
No puedo estresar eso lo suficiente.

(. Sé que es una vieja pregunta, pero me pareció que carecen de una solución práctica)

2

El uso de un C++ - preprocesador estilo, el código de la 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 usted tiene 100 propiedades para hacer frente, eso es ~ 1.200 líneas de código frente a ~ 100. ¿Cuál es más fácil de leer y entender? ¿Cuál es más fácil de escribir?

Con C#, asumiendo que cortar y pegar para crear cada propiedad, eso es 8 pastas por propiedad, 800 en total. Con la macro, sin 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 un número significativo de casos.

Como cualquier herramienta, las macros pueden ser objeto de abuso e incluso pueden ser peligrosas en las manos equivocadas. Para algunos programadores, este es un tema religioso, y los méritos de un enfoque sobre otro son irrelevantes; si eres tú, deberías evitar las macros. Para aquellos de nosotros que regularmente, hábilmente y de forma segura utilizamos herramientas extremadamente nítidas, las macros pueden ofrecer no solo una ganancia de productividad inmediata durante la codificación, sino también en etapas posteriores, durante la depuración y el mantenimiento.