Domanda

Anche se le specifiche C# includono un preprocessore e direttive di base (#define, #if e così via), il linguaggio non ha lo stesso preprocessore flessibile che si trova in linguaggi come C/C++.Credo che la mancanza di un preprocessore così flessibile sia stata una decisione progettuale presa da Anders Hejlsberg (anche se, sfortunatamente, non riesco a trovare riferimenti a questo ora).Per esperienza, questa è sicuramente una buona decisione, poiché c'erano alcune macro davvero terribili e non gestibili create quando stavo facendo molto C/C++.

Detto questo, ci sono una serie di scenari in cui potrei trovare utile un preprocessore leggermente più flessibile.Un codice come il seguente potrebbe essere migliorato con alcune semplici direttive del preprocessore:

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

Sarebbe una buona idea scrivere un preprocessore per gestire casi estremamente semplici come questo?Steve McConnell ha scritto Codice completato (pag.208):

Scrivi il tuo preprocessore Se un linguaggio non include un preprocessore, è abbastanza facile scriverne uno...

Sono combattuto.È stata una decisione progettuale lasciare un preprocessore così flessibile fuori da C#.Tuttavia, un autore che rispetto molto afferma che potrebbe essere ok in alcune circostanze.

Dovrei creare un preprocessore C#?Ce n'è uno disponibile che fa le cose semplici che voglio fare?

È stato utile?

Soluzione

Considera l'idea di dare un'occhiata a una soluzione orientata agli aspetti come PostSharp, che inserisce il codice dopo il fatto in base agli attributi personalizzati.È l'opposto di un precompilatore ma può darti il ​​tipo di funzionalità che stai cercando (notifiche PropertyChanged, ecc.).

Altri suggerimenti

Dovrei creare un preprocessore C#?Ce n'è uno disponibile che fa le cose semplici che voglio fare?

Puoi sempre utilizzare il preprocessore C: C# è abbastanza vicino, dal punto di vista della sintassi.Anche M4 è un'opzione.

So che molte persone pensano che il codice breve equivalga al codice elegante, ma non è vero.

L'esempio che proponi è perfettamente risolto nel codice, come hai dimostrato, a cosa ti serve una direttiva del preprocessore?Non vuoi "preelaborare" il tuo codice, vuoi che il compilatore inserisca del codice per te nelle tue proprietà.È un codice comune ma non è questo lo scopo del preprocessore.

Con il tuo esempio, dove metti il ​​limite?Chiaramente questo soddisfa un modello di osservatore e non c'è dubbio che sarà utile, ma ci sono molte cose che sarebbero utili e che vengono effettivamente fatte perché il codice fornisce flessibilità dove il preprocessore no.Se provi a implementare modelli comuni tramite direttive del preprocessore, ti ritroverai con un preprocessore che deve essere potente quanto il linguaggio stesso.Se lo desidera elaborare il codice in un modo diverso utilizzando una direttiva del preprocessore ma se vuoi solo uno snippet di codice, trova un altro modo perché il preprocessore non era destinato a farlo.

Utilizzando un preprocessore in stile C++, il codice dell'OP potrebbe essere ridotto a questa riga:

 OBSERVABLE_PROPERTY(string, MyProperty)

OBSERVABLE_PROPERTY sarebbe più o meno simile a questo:

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

Se hai 100 proprietà da gestire, si tratta di circa 1.200 righe di codice rispetto a circa 1.200 righe di codice.~100.Quale è più facile da leggere e comprendere?Quale è più facile da scrivere?

Con C#, presupponendo che tu faccia copia e incolla per creare ciascuna proprietà, si tratta di 8 incolla per proprietà, 800 in totale.Con la macro, non si incolla affatto.Quale ha maggiori probabilità di contenere errori di codifica?Che è più facile da modificare se devi aggiungere ad es.una bandiera IsDirty?

Le macro non sono così utili quando è probabile che vi siano variazioni personalizzate in un numero significativo di casi.

Come ogni strumento, si può abusare delle macro e potrebbero persino rivelarsi pericolose nelle mani sbagliate.Per alcuni programmatori si tratta di una questione religiosa e i meriti di un approccio rispetto a un altro sono irrilevanti;se sei tu, dovresti evitare i macro.Per quelli di noi che utilizzano regolarmente, abilmente e in sicurezza strumenti estremamente avanzati, le macro possono offrire non solo un guadagno di produttività immediato durante la codifica, ma anche a valle durante il debug e la manutenzione.

L'argomento principale contro la creazione di un pre-processore per C# è l'integrazione in Visual Studio:ci vorrebbero molti sforzi (se possibile) per far sì che IntelliSense e la nuova compilazione dello sfondo funzionino senza problemi.

Le alternative consistono nell'utilizzare un plug-in di produttività di Visual Studio come ReSharper O CodeRush.Quest'ultimo ha, per quanto ne so, un sistema di template senza eguali e viene fornito con un eccellente refactoring attrezzo.

Un'altra cosa che potrebbe essere utile per risolvere gli esatti tipi di problemi a cui ti riferisci è un framework AOP come PostSharp.
È quindi possibile utilizzare attributi personalizzati per aggiungere funzionalità comuni.

Per ottenere il nome del metodo attualmente eseguito, puoi guardare l'analisi dello stack:

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

Quando ci si trova in un metodo di insieme di proprietà, il nome è set_Property.

Utilizzando la stessa tecnica, puoi anche eseguire query sul file sorgente e sulle informazioni su riga/colonna.

Tuttavia, non l'ho confrontato, creare l'oggetto stacktrace una volta per ogni set di proprietà potrebbe essere un'operazione che richiede troppo tempo.

Penso che probabilmente ti stai perdendo una parte importante del problema quando implementi INotifyPropertyChanged.Il tuo consumatore ha bisogno di un modo per determinare il nome della proprietà.Per questo motivo dovresti avere i nomi delle proprietà definiti come costanti o stringhe statiche di sola lettura, in questo modo il consumatore non deve "indovinare" i nomi delle proprietà.Se utilizzassi un preprocessore, come farebbe il consumatore a sapere qual è il nome della stringa della proprietà?

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

Se stessi progettando la prossima versione di C#, penserei che ogni funzione abbia una variabile locale inclusa automaticamente che contiene il nome della classe e il nome della funzione.Nella maggior parte dei casi, l'ottimizzatore del compilatore lo eliminerebbe.

Non sono sicuro che ci sia molta richiesta per questo genere di cose.

@Jorge ha scritto:Se vuoi elaborare il tuo codice in un modo diverso, usa una direttiva del preprocessore, ma se vuoi solo uno snippet di codice, trova un altro modo perché il preprocessore non era destinato a farlo.

Interessante.Non considero davvero che un preprocessore funzioni necessariamente in questo modo.Nell'esempio fornito, sto eseguendo una semplice sostituzione di testo, che è in linea con la definizione di preprocessore su Wikipedia.

Se questo non è l'uso corretto di un preprocessore, come dovremmo chiamare una semplice sostituzione del testo, che generalmente deve avvenire prima di una compilazione?

Almeno per lo scenario fornito, esiste una soluzione più pulita e sicura rispetto alla creazione di un preprocessore:

Usa i generici.Così:

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

Questo può essere facilmente esteso per restituire a PropertyInfo invece, permettendoti di ottenere molte più cose oltre al semplice nome della proprietà.

Dal momento che è un Extension method, puoi utilizzare questo metodo praticamente su ogni oggetto.


Inoltre, questo è indipendente dai tipi.
Non posso sottolinearlo abbastanza.

(So ​​che è una vecchia domanda, ma ho trovato che mancava una soluzione pratica.)

Se sei pronto ad abbandonare C# potresti voler dare un'occhiata a Boh linguaggio che è incredibilmente flessibile macro supporto attraverso AST (Albero sintattico astratto).È davvero un'ottima cosa se riesci ad abbandonare il linguaggio C#.

Per ulteriori informazioni su Boo vedere queste domande correlate:

Autorizzato sotto: CC-BY-SA insieme a attribuzione
Non affiliato a StackOverflow
scroll top