Frage

Während die C#-Spezifikation einen Präprozessor und grundlegende Anweisungen (#define, #if usw.) enthält, verfügt die Sprache nicht über denselben flexiblen Präprozessor, der in Sprachen wie C/C++ zu finden ist.Ich glaube, dass das Fehlen eines solch flexiblen Vorprozessors eine Designentscheidung von Anders Hejlsberg war (obwohl ich derzeit leider keinen Hinweis darauf finden kann).Aus Erfahrung ist dies sicherlich eine gute Entscheidung, da damals, als ich viel mit C/C++ gearbeitet habe, einige wirklich schreckliche, nicht wartbare Makros erstellt wurden.

Allerdings gibt es eine Reihe von Szenarien, in denen ich einen etwas flexibleren Vorprozessor als nützlich erachten könnte.Code wie der folgende könnte durch einige einfache Präprozessoranweisungen verbessert werden:

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

Wäre es eine gute Idee, einen Präprozessor zu schreiben, um extrem einfache Fälle wie diesen zu verarbeiten?Steve McConnell hat geschrieben Code abgeschlossen (S. 208):

Schreiben Sie Ihren eigenen Präprozessor Wenn eine Sprache keinen Präprozessor enthält, ist es ziemlich einfach, einen zu schreiben ...

Ich bin zerrissen.Es war eine Designentscheidung, einen derart flexiblen Präprozessor aus C# herauszulassen.Ein Autor, den ich sehr respektiere, erwähnt jedoch, dass es unter bestimmten Umständen in Ordnung sein könnte.

Sollte ich einen C#-Präprozessor erstellen?Gibt es eines, das die einfachen Dinge erledigt, die ich tun möchte?

War es hilfreich?

Lösung

Erwägen Sie einen Blick auf eine aspektorientierte Lösung wie PostSharp, das nachträglich Code basierend auf benutzerdefinierten Attributen einfügt.Es ist das Gegenteil eines Precompilers, kann Ihnen aber die Art von Funktionalität bieten, die Sie suchen (PropertyChanged-Benachrichtigungen usw.).

Andere Tipps

Sollte ich einen C#-Präprozessor erstellen?Gibt es eines, das die einfachen Dinge erledigt, die ich tun möchte?

Sie können jederzeit den C-Präprozessor verwenden – C# ist syntaktisch nah genug dran.M4 ist auch eine Option.

Ich weiß, dass viele Leute denken, kurzer Code sei gleich eleganter Code, aber das stimmt nicht.

Das von Ihnen vorgeschlagene Beispiel ist, wie Sie gezeigt haben, perfekt im Code gelöst. Wofür benötigen Sie eine Präprozessoranweisung?Sie möchten Ihren Code nicht „vorverarbeiten“, sondern möchten, dass der Compiler Code für Sie in Ihre Eigenschaften einfügt.Es handelt sich um allgemeinen Code, aber das ist nicht der Zweck des Präprozessors.

Mit Ihrem Beispiel: Wo setzen Sie die Grenze?Das erfüllt eindeutig ein Beobachtermuster und es besteht kein Zweifel daran, dass es nützlich sein wird, aber es gibt eine Menge nützlicher Dinge, die tatsächlich getan werden, weil der Code dies vorsieht Flexibilität Wobei der Präprozessor dies nicht tut.Wenn Sie versuchen, allgemeine Muster durch Präprozessoranweisungen zu implementieren, erhalten Sie am Ende einen Präprozessor, der genauso leistungsfähig sein muss wie die Sprache selbst.Wenn Sie wollen Verarbeiten Sie Ihren Code auf eine andere Art und Weise, indem Sie eine Präprozessoranweisung verwenden Wenn Sie jedoch nur einen Codeausschnitt möchten, finden Sie einen anderen Weg, da der Präprozessor dafür nicht vorgesehen ist.

Mit einem Präprozessor im C++-Stil könnte der Code des OP auf diese eine Zeile reduziert werden:

 OBSERVABLE_PROPERTY(string, MyProperty)

OBSERVABLE_PROPERTY würde ungefähr so ​​aussehen:

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

Wenn Sie 100 Eigenschaften verwalten müssen, sind das etwa 1.200 Codezeilen im Vergleich zu~100.Was ist leichter zu lesen und zu verstehen?Was ist einfacher zu schreiben?

Unter der Annahme, dass Sie in C# jede Eigenschaft ausschneiden und einfügen, sind das 8 Einfügungen pro Eigenschaft, also insgesamt 800.Mit dem Makro überhaupt kein Einfügen.Was enthält mit größerer Wahrscheinlichkeit Codierungsfehler?Was einfacher zu ändern ist, wenn Sie z. B. hinzufügen müsseneine IsDirty-Flagge?

Makros sind nicht so hilfreich, wenn es in einer erheblichen Anzahl von Fällen wahrscheinlich zu benutzerdefinierten Variationen kommt.

Wie jedes Werkzeug können Makros missbraucht werden und in den falschen Händen sogar gefährlich sein.Für einige Programmierer ist dies eine religiöse Angelegenheit, und die Vorzüge eines Ansatzes gegenüber einem anderen sind irrelevant.Wenn Sie das sind, sollten Sie Makros vermeiden.Für diejenigen unter uns, die extrem scharfe Werkzeuge regelmäßig, geschickt und sicher verwenden, können Makros nicht nur einen unmittelbaren Produktivitätsgewinn beim Codieren, sondern auch nachgelagert beim Debuggen und bei der Wartung bieten.

Das Hauptargument gegen den Aufbau eines Vorprozessors für C# ist die Integration in Visual Studio:Es würde viel Aufwand erfordern (wenn überhaupt möglich), Intellisense und die neue Hintergrundkompilierung nahtlos zum Laufen zu bringen.

Alternativen sind die Verwendung eines Visual Studio-Produktivitäts-Plugins wie ReSharper oder CodeRush.Letzteres verfügt meines Wissens nach über ein unübertroffenes Vorlagensystem und verfügt über eine hervorragende Umgestaltung Werkzeug.

Eine weitere Sache, die bei der Lösung der genauen Arten von Problemen, auf die Sie sich beziehen, hilfreich sein könnte, ist ein AOP-Framework wie PostSharp.
Anschließend können Sie benutzerdefinierte Attribute verwenden, um allgemeine Funktionen hinzuzufügen.

Um den Namen der aktuell ausgeführten Methode zu erhalten, können Sie sich den Stacktrace ansehen:

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

Wenn Sie sich in einer Property-Set-Methode befinden, lautet der Name set_Property.

Mit der gleichen Technik können Sie auch die Quelldatei und Zeilen-/Spalteninformationen abfragen.

Ich habe dies jedoch nicht verglichen, da das einmalige Erstellen des Stacktrace-Objekts für jeden Eigenschaftssatz möglicherweise ein zu zeitaufwändiger Vorgang ist.

Ich denke, Sie übersehen möglicherweise einen wichtigen Teil des Problems, wenn Sie INotifyPropertyChanged implementieren.Ihr Verbraucher benötigt eine Möglichkeit, den Immobiliennamen zu bestimmen.Aus diesem Grund sollten Sie Ihre Eigenschaftsnamen als Konstanten oder statische schreibgeschützte Zeichenfolgen definieren, damit der Verbraucher die Eigenschaftsnamen nicht „erraten“ muss.Wenn Sie einen Präprozessor verwenden würden, wie würde der Verbraucher dann wissen, wie der Stringname der Eigenschaft lautet?

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

Wenn ich die nächste Version von C# entwerfen würde, würde ich darüber nachdenken, dass jede Funktion automatisch eine lokale Variable enthält, die den Namen der Klasse und den Namen der Funktion enthält.In den meisten Fällen würde der Optimierer des Compilers es entfernen.

Ich bin mir allerdings nicht sicher, ob es für so etwas eine große Nachfrage gibt.

@Jorge hat geschrieben:Wenn Sie Ihren Code auf andere Weise verarbeiten möchten, verwenden Sie eine Präprozessoranweisung. Wenn Sie jedoch nur einen Codeausschnitt möchten, finden Sie einen anderen Weg, da der Präprozessor dafür nicht vorgesehen ist.

Interessant.Ich glaube nicht wirklich, dass ein Präprozessor unbedingt auf diese Weise funktioniert.Im bereitgestellten Beispiel führe ich eine einfache Textersetzung durch, die mit der Definition eines Präprozessors übereinstimmt Wikipedia.

Wenn dies nicht die ordnungsgemäße Verwendung eines Präprozessors ist, wie sollten wir dann eine einfache Textersetzung nennen, die im Allgemeinen vor einer Kompilierung erfolgen muss?

Zumindest für das bereitgestellte Szenario gibt es eine sauberere, typsichere Lösung als den Aufbau eines Präprozessors:

Verwenden Sie Generika.Etwa so:

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

Dies kann leicht erweitert werden, um a zurückzugeben PropertyInfo Stattdessen erhalten Sie so viel mehr Informationen als nur den Namen der Immobilie.

Da es ein ist Extension method, können Sie diese Methode auf praktisch jedes Objekt anwenden.


Außerdem ist dies typsicher.
Ich kann das nicht genug betonen.

(Ich weiß, dass es eine alte Frage ist, aber ich fand, dass es an einer praktischen Lösung mangelt.)

Wenn Sie bereit sind, auf C# zu verzichten, sollten Sie sich das ansehen Buh Sprache, die unglaublich flexibel ist Makro Unterstützung durch AST (Abstrakter Syntaxbaum)-Manipulationen.Es ist wirklich großartig, wenn man auf die Sprache C# verzichten kann.

Weitere Informationen zu Boo finden Sie in den folgenden verwandten Fragen:

Lizenziert unter: CC-BY-SA mit Zuschreibung
Nicht verbunden mit StackOverflow
scroll top