Question

Bien que la spécification C# inclut un préprocesseur et des directives de base (#define, #if, etc.), le langage ne dispose pas du même préprocesseur flexible que l'on trouve dans des langages tels que C/C++.Je pense que l'absence d'un préprocesseur aussi flexible était une décision de conception prise par Anders Hejlsberg (même si, malheureusement, je ne trouve pas de référence à cela pour le moment).Par expérience, c'est certainement une bonne décision, car il y avait des macros vraiment terribles et non maintenables créées à l'époque où je faisais beaucoup de C/C++.

Cela dit, il existe un certain nombre de scénarios dans lesquels je pourrais trouver utile un préprocesseur légèrement plus flexible.Un code tel que celui-ci pourrait être amélioré par quelques directives simples du pré-processeur :

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

Serait-ce une bonne idée d’écrire un pré-processeur pour gérer des cas extrêmement simples comme celui-ci ?Steve McConnell a écrit dans Code terminé (p208) :

Écrivez votre propre préprocesseur Si un langage n'inclut pas de préprocesseur, il est assez facile d'en écrire un...

Je suis déchiré.C'était une décision de conception de laisser un préprocesseur aussi flexible en dehors de C#.Cependant, un auteur que je respecte beaucoup mentionne que cela peut être acceptable dans certaines circonstances.

Dois-je créer un préprocesseur C# ?Y en a-t-il un qui fasse les choses simples que je veux faire ?

Était-ce utile?

La solution

Pensez à jeter un œil à une solution orientée aspect comme PostSharp, qui injecte du code après coup en fonction d'attributs personnalisés.C'est l'opposé d'un précompilateur mais peut vous offrir le type de fonctionnalités que vous recherchez (notifications PropertyChanged, etc.).

Autres conseils

Dois-je créer un préprocesseur C# ?Y en a-t-il un qui fasse les choses simples que je veux faire ?

Vous pouvez toujours utiliser le préprocesseur C – C# est assez proche, du point de vue de la syntaxe.M4 est également une option.

Je sais que beaucoup de gens pensent qu’un code court est synonyme de code élégant, mais ce n’est pas vrai.

L'exemple que vous proposez est parfaitement résolu en code, comme vous l'avez montré, pourquoi avez-vous besoin d'une directive de préprocesseur ?Vous ne voulez pas "prétraiter" votre code, vous voulez que le compilateur insère du code pour vous dans vos propriétés.C'est du code commun mais ce n'est pas le but du préprocesseur.

Avec votre exemple, Où mettez-vous la limite ?De toute évidence, cela satisfait un modèle d'observateur et il ne fait aucun doute que cela sera utile, mais il y a beaucoup de choses qui seraient utiles et qui sont réellement faites parce que le code fournit la flexibilité alors que le préprocesseur ne le fait pas.Si vous essayez d'implémenter des modèles communs via des directives de préprocesseur, vous obtiendrez un préprocesseur qui doit être aussi puissant que le langage lui-même.Si tu veux traitez votre code d'une manière différente en utilisant une directive de préprocesseur mais si vous voulez juste un extrait de code, trouvez un autre moyen car le préprocesseur n'était pas censé faire cela.

En utilisant un préprocesseur de style C++, le code de l'OP pourrait être réduit à cette seule ligne :

 OBSERVABLE_PROPERTY(string, MyProperty)

OBSERVABLE_PROPERTY ressemblerait plus ou moins à ceci :

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

Si vous avez 100 propriétés à gérer, cela représente environ 1 200 lignes de code par rapport à 1 200 lignes de code.~100.Qu’est-ce qui est le plus facile à lire et à comprendre ?Qu'est-ce qui est le plus facile à écrire ?

Avec C#, en supposant que vous effectuez un copier-coller pour créer chaque propriété, cela représente 8 collages par propriété, soit 800 au total.Avec la macro, pas de collage du tout.Qu’est-ce qui est le plus susceptible de contenir des erreurs de codage ?Ce qui est plus facile à modifier si vous devez ajouter par ex.un drapeau IsDirty ?

Les macros ne sont pas aussi utiles lorsqu'il est probable qu'il y ait des variations personnalisées dans un nombre important de cas.

Comme tout outil, les macros peuvent être utilisées à mauvais escient et peuvent même être dangereuses entre de mauvaises mains.Pour certains programmeurs, il s’agit d’une question religieuse et les mérites d’une approche par rapport à une autre ne sont pas pertinents ;si c'est votre cas, vous devriez éviter les macros.Pour ceux d’entre nous qui utilisent régulièrement, habilement et en toute sécurité des outils extrêmement pointus, les macros peuvent offrir non seulement un gain de productivité immédiat lors du codage, mais également en aval lors du débogage et de la maintenance.

Le principal argument contre la création d'un pré-processeur pour C# est l'intégration dans Visual Studio :il faudrait beaucoup d'efforts (si possible) pour que Intellisense et la nouvelle compilation en arrière-plan fonctionnent de manière transparente.

Les alternatives consistent à utiliser un plugin de productivité Visual Studio comme ReSharper ou CodeRush.Ce dernier dispose - à ma connaissance - d'un système de modèles inégalé et est livré avec un excellent refactorisation outil.

Une autre chose qui pourrait être utile pour résoudre les types exacts de problèmes auxquels vous faites référence est un framework AOP comme PostSharp.
Vous pouvez ensuite utiliser des attributs personnalisés pour ajouter des fonctionnalités communes.

Pour obtenir le nom de la méthode actuellement exécutée, vous pouvez consulter la trace de la pile :

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

Lorsque vous êtes dans une méthode de jeu de propriétés, le nom est set_Property.

En utilisant la même technique, vous pouvez également interroger le fichier source et les informations de ligne/colonne.

Cependant, je n'ai pas comparé cela, créer l'objet stacktrace une fois pour chaque ensemble de propriétés pourrait être une opération trop longue.

Je pense qu'il vous manque peut-être une partie importante du problème lors de l'implémentation de INotifyPropertyChanged.Votre consommateur a besoin d'un moyen de déterminer le nom de la propriété.Pour cette raison, vos noms de propriété doivent être définis comme des constantes ou des chaînes statiques en lecture seule, de cette façon le consommateur n'a pas à « deviner » les noms de propriété.Si vous utilisiez un préprocesseur, comment le consommateur saurait-il quel est le nom de chaîne de la propriété ?

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 je concevais la prochaine version de C#, je penserais à chaque fonction ayant une variable locale automatiquement incluse contenant le nom de la classe et le nom de la fonction.Dans la plupart des cas, l'optimiseur du compilateur le supprimerait.

Je ne suis cependant pas sûr qu'il y ait une grande demande pour ce genre de chose.

@Jorge a écrit :Si vous souhaitez traiter votre code d'une manière différente, utilisez une directive de préprocesseur, mais si vous souhaitez simplement un extrait de code, trouvez un autre moyen car le préprocesseur n'est pas censé faire cela.

Intéressant.Je ne considère pas vraiment qu'un préprocesseur fonctionne nécessairement de cette façon.Dans l'exemple fourni, j'effectue une simple substitution de texte, qui correspond à la définition d'un préprocesseur sur Wikipédia.

Si ce n'est pas une bonne utilisation d'un préprocesseur, comment devrions-nous appeler un simple remplacement de texte, qui doit généralement avoir lieu avant une compilation ?

Au moins pour le scénario fourni, il existe une solution plus propre et plus sûre que la création d'un préprocesseur :

Utilisez des génériques.Ainsi:

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

Cela peut facilement être étendu pour renvoyer un PropertyInfo au lieu de cela, vous permettant d'obtenir bien plus d'informations que le simple nom de la propriété.

Puisque c'est un Extension method, vous pouvez utiliser cette méthode sur pratiquement tous les objets.


De plus, c'est de type sécurisé.
Je ne saurais trop insister là-dessus.

(Je sais que c'est une vieille question, mais j'ai trouvé qu'il lui manquait une solution pratique.)

Si vous êtes prêt à abandonner C#, vous voudrez peut-être consulter le Huer langage incroyablement flexible macro un soutien à travers AST (Arbre de syntaxe abstraite).C'est vraiment génial si vous pouvez abandonner le langage C#.

Pour plus d’informations sur Boo, consultez ces questions connexes :

Licencié sous: CC-BY-SA avec attribution
Non affilié à StackOverflow
scroll top