C # -Makrodefinitionen im Präprozessor

74

Kann C # Makros wie in der Programmiersprache C mit Pre-Prozessor-Anweisungen definieren? Ich möchte die regelmäßige Eingabe bestimmter sich wiederholender Anweisungen wie der folgenden vereinfachen:

Console.WriteLine("foo");
Brian Tompsett - 汤 莱恩
quelle
29
Übrigens können Sie für diese bestimmte Methode cwin Visual Studio schreiben und die Tabulatortaste drücken.
MasterMastic
2
Es gibt einen Makroprozessor im Lisp-Stil für C # namens LeMP . Makros im Lisp-Stil sind C / C ++ - Makros weit überlegen. Aber in neuen Versionen von C # können Sie verkürzen , Console.WriteLineum WriteLinenach Zugabe using static System.Console;an der Spitze einer Quelldatei.
Qwertie

Antworten:

53

Nein, C # unterstützt keine Präprozessor-Makros wie C. Visual Studio verfügt dagegen über Snippets . Die Ausschnitte von Visual Studio sind eine Funktion der IDE und werden im Editor erweitert, anstatt beim Kompilieren durch einen Präprozessor im Code ersetzt zu werden.

Andrew Hare
quelle
26
@ weberc2 schlechte Programmierer sollten elegante Funktionalität für den Rest von uns nicht verderben
Jake
22
@ Jake-Makros sind keine "elegante Funktionalität", sondern eine Kompensation für schlechtes Sprachdesign.
weberc2
69
@ weberc2 Makros sind sehr hilfreich, wenn dieselbe Codebasis auf Ziele zur Kompilierungszeit wie iOS, Android, MacOSX usw. abzielen muss. Das Fehlen von Makros bedeutet, dass Sie #if/#elseif/#elsebei einigen einfachen Makros viel Code zwischen Pragmas einfügen müssen man könnte den richtigen Code für jedes Ziel auf effiziente / elegante und wartbare Weise ausgeben. Kurz gesagt, Makros im C-Stil sind sehr hilfreich, wenn Sie Code einfügen müssen, der auf einigen Zielen einfach nicht kompiliert werden kann.
Johnny Lambada
19
@ weberc2 Das Problem bei dieser "besseren Lösung" ist, dass sie schwer ist und einige einfache Codezeilen über mehrere Dateien verteilt, die alle verstanden werden müssen, um die vollständige Lösung zu verstehen. Ich stimme nicht zu, dass das besser ist. Ja, der C-Präprozessor wurde bis zur Qual missbraucht (ich war definitiv ein Missbraucher!), Aber es war auch in vielen Situationen sehr nützlich, Code viel einfacher und verständlicher zu machen.
Johnny Lambada
6
@ weberc2 Wir sprechen über zwei verschiedene Dinge. Ich sage nicht, dass ein Haufen #ifdefsumliegender großer Codeblöcke gut ist. Es ist nicht. Ich sage, dass die gerichtliche Verwendung von #define MACRO(x,y)eingebettet in #ifdef PLATFORM1 #elif PLATFORM2 ...in begrenzten Fällen als Werkzeug in der Toolbox nützlich sein kann. Vielleicht sind Sie auch damit nicht einverstanden - nach einer Google-Suche zur Verteidigung des Präprozessors scheint dies auch für den größten Teil der Welt der Fall zu sein. Aber ich bin auf dieses Juwel gestoßen.
Johnny Lambada
43

Sie können einen C-Präprozessor (wie mcpp) verwenden und ihn in Ihre .csproj-Datei einbinden. Dann ändern Sie "Build Action" für Ihre Quelldatei von Compile to Preprocess oder wie auch immer Sie es nennen. Fügen Sie einfach BeforBuild zu Ihrem CSPROJ wie folgt aus :

  <Target Name="BeforeBuild" Inputs="@(Preprocess)" Outputs="@(Preprocess->'%(Filename)_P.cs')">
<Exec Command="..\Bin\cpp.exe @(Preprocess) -P -o %(RelativeDir)%(Filename)_P.cs" />
<CreateItem Include="@(Preprocess->'%(RelativeDir)%(Filename)_P.cs')">
  <Output TaskParameter="Include" ItemName="Compile" />
</CreateItem>

Möglicherweise müssen Sie die Kompilierung für mindestens eine Datei (in einem Texteditor) manuell in Vorverarbeitung ändern. Dann sollte die Option "Vorverarbeitung" in Visual Studio zur Auswahl verfügbar sein.

Ich weiß, dass Makros stark überbeansprucht und missbraucht werden, aber das vollständige Entfernen ist genauso schlecht, wenn nicht schlimmer. Ein klassisches Beispiel für die Verwendung von Makros wäre NotifyPropertyChanged . Jeder Programmierer, der diesen Code tausende Male von Hand umschreiben musste, weiß, wie schmerzhaft er ohne Makros ist.

user2224389
quelle
15
Für eine „innovative“ Lösung und für ein Denken über den Tellerrand hinaus muss man Anerkennung finden. Aber nur ein Ratschlag an alle, die diese "Lösung" lesen, bitte tun Sie es nicht. Es gibt einige Dinge, die einfach falsch sind, die so ein Hack sind. Dies ist eines dieser Dinge.
Danpalmer
15
@danpalmer Warum sollte das Hinzufügen eines CPP-Shellouts MyCommon.targetseinfach falsch sein ? Es gibt nur einige Dinge, die CPP tun kann, die die Sprache unglaublich schwierig macht. IMO, es ist schlimmer, dass der Entwickler beim Schreiben einen Argumentnamen tatsächlich manuell stringifizieren muss throw new ArgumentNullException("argumentName");. Code-Verträge sollten das lösen, aber es erfordert einen IL-Umschreiber, der angeschraubt .targetsist. Es sollte nicht schlimmer sein, einen CPP-Aufruf anzulegen .
Binki
Da dieser Beitrag aus dem Jahr 2013 stammt, möchte ich an C # 6 erinnern nameof(argumentX). C # 6 und 7 fügten viele tiefgreifende und direkte Lösungen für triviale Probleme hinzu, die früher Schmerzen verursachten, wie dieses. Und ich muss @danpalmer ehrlich zustimmen - ja, je mehr Hacks wie diese hinzugefügt werden, desto unwahrscheinlicher ist es, dass jemand ein Projekt pflegen oder sogar einige Jahre später kompilieren möchte.
Bytecode77
1
Bis C # Makros implementiert, ist dies bei weitem die beste Lösung, in der Tat ein Kinderspiel, ein Volltreffer. Ich kann das nicht genug qualifizieren. Die Tatsache, dass es nicht von der Mehrheit verwendet wird, hat nichts damit zu tun, dass es die perfekte Lösung ist. Übrigens, meiner Meinung nach sollte dies allgegenwärtig angenommen werden.
Mireazma
1
@mireazma Intellisense funktioniert jedoch nicht mehr. Es ist also kein Kinderspiel
Ayxan Haqverdili
28

Ich benutze dies, um Folgendes zu vermeiden Console.WriteLine(...):

public static void Cout(this string str, params object[] args) { 
    Console.WriteLine(str, args);
}

und dann können Sie Folgendes verwenden:

"line 1".Cout();
"This {0} is an {1}".Cout("sentence", "example");

Es ist prägnant und irgendwie funky.

Anthonybell
quelle
2
Sie müssen also nicht tippen Console.WriteLine(...)(was ziemlich lang ist, um häufig tippen zu müssen). Sie könnten Ihre eigene Methode schreiben, um dies zu tun, aber die Verwendung einer String-Erweiterung ist meiner Meinung nach etwas eleganter.
Anthonybell
6
Es dauert nicht lange, wenn Sie die Tab-Vervollständigung verwenden. Sie erstellen zwei Möglichkeiten, um genau dasselbe zu tun, was andere Entwickler verwirrt. Außerdem ist es nicht eleganter; Vielleicht sehen Sie so etwas in Python, aber in C # ist es komisch.
Mpen
4
+1 für eine Erweiterungsmethode. Obwohl ... da C # nicht C ++ ist, könnte ich es persönlich so etwas wie .ToConsole () anstelle von Cout () nennen. Zugegeben, das "C" in "Cout" bedeutet Konsole, und .ToConsole () ist länger, aber .ToConsole () ist auch ein häufigeres allgemeines .NET-Muster und wahrscheinlich sinnvoller für jemanden, der nicht aus einem C ++ - Hintergrund stammt, und Intellisense nimmt es auf und lässt Sie einfach .ToC eingeben und die Leertaste drücken, um es trotzdem zu vervollständigen.
Craig
4
-1: Dies ist nicht nur ein sehr spezifischer Anwendungsfall, der im Allgemeinen nicht einfach angewendet werden kann, sondern es ist auch nicht einmal ein Fall, in dem Präprozessormakros gerechtfertigt sind.
Nuzzolilo
6
Dies ist kein Präprozessor-Makro. Die Lösung für diese Frage lautet eindeutig: "C # unterstützt keine Präprozessor-Makros wie c"
Anthonybell
12

Während Sie keine Makros schreiben können, bietet C # 6.0 jetzt statische Verwendungen, wenn es darum geht, Dinge wie Ihr Beispiel zu vereinfachen. Hier ist das Beispiel, das Martin Pernica in seinem Medium-Artikel gegeben hat :

using static System.Console; // Note the static keyword

namespace CoolCSharp6Features
{
  public class Program
  {
    public static int Main(string[] args)
    {
      WriteLine("Hellow World without Console class name prefix!");

      return 0;
    }
  }
}
Sraboy
quelle
6

Es gibt kein direktes Äquivalent zu Makros im C-Stil in C #, aber inlined statische Methoden - mit oder ohne #if/ #elseif/ #elsePragmas - sind die nächsten, die Sie erhalten können:

        /// <summary>
        /// Prints a message when in debug mode
        /// </summary>
        [MethodImpl(MethodImplOptions.AggressiveInlining)]
        public static unsafe void Log(object message) {
#if DEBUG
            Console.WriteLine(message);
#endif
        }

        /// <summary>
        /// Prints a formatted message when in debug mode
        /// </summary>
        /// <param name="format">A composite format string</param>
        /// <param name="args">An array of objects to write using format</param>
        [MethodImpl(MethodImplOptions.AggressiveInlining)]
        public static unsafe void Log(string format, params object[] args) {
#if DEBUG
            Console.WriteLine(format, args);
#endif
        }

        /// <summary>
        /// Computes the square of a number
        /// </summary>
        /// <param name="x">The value</param>
        /// <returns>x * x</returns>
        [MethodImpl(MethodImplOptions.AggressiveInlining)]
        public static double Square(double x) {
            return x * x;
        }

        /// <summary>
        /// Wipes a region of memory
        /// </summary>
        /// <param name="buffer">The buffer</param>
        [MethodImpl(MethodImplOptions.AggressiveInlining)]
        public static unsafe void ClearBuffer(ref byte[] buffer) {
            ClearBuffer(ref buffer, 0, buffer.Length);
        }

        /// <summary>
        /// Wipes a region of memory
        /// </summary>
        /// <param name="buffer">The buffer</param>
        /// <param name="offset">Start index</param>
        /// <param name="length">Number of bytes to clear</param>
        [MethodImpl(MethodImplOptions.AggressiveInlining)]
        public static unsafe void ClearBuffer(ref byte[] buffer, int offset, int length) {
            fixed(byte* ptrBuffer = &buffer[offset]) {
                for(int i = 0; i < length; ++i) {
                    *(ptrBuffer + i) = 0;
                }
            }
        }

Dies funktioniert perfekt als Makro, hat jedoch einen kleinen Nachteil: Mit inlined gekennzeichnete Methoden werden wie jede andere "normale" Methode in den Reflexionsteil Ihrer Baugruppe kopiert.

Binkan Salaryman
quelle
1
Können Sie mit [ConditionalAttribute("DEBUG")]diesen Methoden den gleichen Effekt wie mit dem #ifs erzielen ?
jpmc26
@ jpmc26 Siehe stackoverflow.com/questions/3788605/…
Binkan Salaryman
3

Glücklicherweise hat C # keinen Präprozessor im C / C ++ - Stil - nur bedingte Kompilierung und Pragmas (und möglicherweise etwas anderes, an das ich mich nicht erinnern kann) werden unterstützt. Leider verfügt C # über keine Metaprogrammierfunktionen (dies kann in gewissem Maße tatsächlich mit Ihrer Frage zusammenhängen).

Anton Gogolev
quelle
5
Ich würde sagen, es ist nicht die Aufgabe der Programmiersprache, einen guten Stil durchzusetzen. C # hat Goto's und ein Entwickler hat die Intelligenz, sie nicht zu benutzen. Das gleiche gilt für Makros, aber manchmal möchte ich sie wirklich haben!
Bytecode77
Beispiel: "Dispatcher.Invoke (Delegat" wäre schön als Makro in Ihrem WPF-Code zu haben.
Bytecode77
@ bytecode77 Gotos sind in C #, C und C ++ nützlich, um aus verschachtelten Schleifen herauszuspringen. Ein Ansatz wie Javas Continue / Break to Label ist intelligenter, aber dafür sind Gotos in Ordnung. Makros sind jedoch in einer Sprache nicht nützlich, in der die Unterstützung für mehrere Plattformen von der Sprache übernommen wird.
Winter
1
Obwohl ich sowohl Vor- als auch Nachteile für alles ermutige, sehe ich nicht, wie das Herauskommen aus einer Schleife einer booleschen quitVariablen überlegen ist. Sobald verschachtelte Anweisungen vorhanden sind, ist Code schwer zu lesen und zu warten. PHP hat die break 2..n;Anweisung, C # nicht. C # verfügt jedoch über genügend LINQ-Erweiterungen, um in vielen Fällen keine verschachtelten Schleifen zu verwenden, sodass der Code auf andere Weise lesbar ist - was ich ehrlich bevorzuge.
Bytecode77
Wir haben hier besprochen, wie man mit handgeschriebenen Profilen einer Hot-Loop umgeht. Zum Glück ist eine wirklich schlechte Wortwahl.
Joshua
1

Verwandeln Sie das C-Makro in eine statische C # -Methode in einer Klasse.

Robert
quelle
5
Dadurch erhalten Sie keine Unterstützung für die CPP-Makro-Stringifizierung. Makros sind nützlich, weil sie den Code eher wie einfachen Text als wie Code behandeln. Das OP scheint jedoch keine Makros zu wollen / brauchen. Für seine Zwecke wäre dies eine absolut gültige (und bessere) Lösung.
Binki
1

Ich würde Ihnen vorschlagen, eine Erweiterung zu schreiben, so etwas wie unten.

public static class WriteToConsoleExtension
{
   // Extension to all types
   public static void WriteToConsole(this object instance, 
                                     string format, 
                                     params object[] data)
   {
       Console.WriteLine(format, data);
   }
}

class Program
{
    static void Main(string[] args)
    {
        Program p = new Program();
        // Usage of extension
        p.WriteToConsole("Test {0}, {1}", DateTime.Now, 1);
    }
}

Hoffe das hilft (und nicht zu spät :))

Milan Jaric
quelle
5
Für mich ist das nur verwirrend
LuckyLikey
2
Extensionitis , wie ich es manchmal nenne. Das Erstellen einer API-DLL führt für mich häufig zu immer mehr Erweiterungen und nicht zu Methoden. ZB ValidateUrl(this string)- Etwas, das ich mittlerweile in einer Klasse bevorzuge, da dies dazu neigt, Intellisense (besonders bei this object) aufzublähen und es manchmal dunkel macht, solche Methoden zu finden. Nachdem Sie Validate.Url(string)den Code nicht sprengen und ist offenbar leicht für andere zu finden und zu nutzen.
Bytecode77
Ich stimme Ihnen zu
Milan Jaric
0

Da C # 7.0 using staticDirektiven- und lokale Funktionen unterstützt, benötigen Sie in den meisten Fällen keine Präprozessormakros.

Moien
quelle
-2

Ich werde einen C # -Makrocompiler namens C # -Makros erstellen.

Fortschritt ist hier .


quelle
Das ist keine Antwort und der Link wurde nicht gefunden.
Saher Ahwal