Suche…


Einführung

Diese sechste Iteration der C # -Sprache wird vom Roslyn-Compiler bereitgestellt. Dieser Compiler wurde mit Version 4.6 von .NET Framework veröffentlicht. Er kann jedoch abwärtskompatibel Code generieren, um frühere Framework-Versionen als Ziel festzulegen. C # -Version 6-Code kann vollständig abwärtskompatibel zu .NET 4.0 kompiliert werden. Es kann auch für frühere Frameworks verwendet werden, einige Funktionen, die zusätzliche Framework-Unterstützung erfordern, funktionieren jedoch möglicherweise nicht ordnungsgemäß.

Bemerkungen

Die sechste Version von C # wurde im Juli 2015 zusammen mit Visual Studio 2015 und .NET 4.6 veröffentlicht.

Es enthält nicht nur einige neue Sprachfunktionen, sondern auch eine vollständige Umschreibung des Compilers. Zuvor war csc.exe eine native Win32-Anwendung, die in C ++ geschrieben wurde. Mit C # 6 ist es jetzt eine in C # geschriebene .NET-verwaltete Anwendung. Diese Umschreibung wurde als Projekt "Roslyn" bezeichnet und der Code ist jetzt Open Source und auf GitHub verfügbar.

Betreiber Nameof

Der nameof Operator gibt den Namen eines nameof als string . Dies ist nützlich, wenn Ausnahmen im Zusammenhang mit Methodenargumenten INotifyPropertyChanged und auch INotifyPropertyChanged implementiert INotifyPropertyChanged .

public string SayHello(string greeted)
{
    if (greeted == null)
        throw new ArgumentNullException(nameof(greeted));
    
    Console.WriteLine("Hello, " + greeted);
}

Der nameof Operator wird zur Kompilierzeit ausgewertet und wandelt den Ausdruck in ein String-Literal um. Dies ist auch nützlich für Zeichenfolgen, die nach ihrem Member benannt werden, das sie verfügbar macht. Folgendes berücksichtigen:

public static class Strings
{
    public const string Foo = nameof(Foo); // Rather than Foo = "Foo"
    public const string Bar = nameof(Bar); // Rather than Bar = "Bar"
}

Da nameof Ausdrücke Kompilierung-Konstanten sind, können sie in Attribute, werden verwendet case Etiketten, switch Anweisungen, und so weiter.


Es ist praktisch, nameof mit Enum s zu verwenden. Anstatt:

Console.WriteLine(Enum.One.ToString());

es ist möglich zu verwenden:

Console.WriteLine(nameof(Enum.One))

Die Ausgabe wird in beiden Fällen One .


Der nameof Operator kann mit einer statischen Syntax auf nicht statische Member zugreifen. Statt zu tun:

string foo = "Foo";
string lengthName = nameof(foo.Length);

Kann ersetzt werden durch:

string lengthName = nameof(string.Length);

Die Ausgabe wird in beiden Beispielen Length . Letzteres verhindert jedoch die Erstellung unnötiger Instanzen.


Obwohl der nameof Operators mit den meisten Sprachkonstrukten funktioniert, gibt es einige Einschränkungen. Beispielsweise können Sie den Operator nameof für offene generische Typen oder Methodenrückgabewerte verwenden:

public static int Main()
{   
    Console.WriteLine(nameof(List<>)); // Compile-time error
    Console.WriteLine(nameof(Main())); // Compile-time error
}

Wenn Sie ihn auf einen generischen Typ anwenden, wird der generische Typparameter ignoriert:

Console.WriteLine(nameof(List<int>));  // "List"
Console.WriteLine(nameof(List<bool>)); // "List"

Weitere Beispiele finden Sie zu diesem Thema gewidmet nameof .


Problemumgehung für frühere Versionen ( mehr Details )

Obwohl der nameof Operator für Versionen vor 6.0 nicht in C # vorhanden ist, kann mit MemberExpression ähnliche Funktionalität wie in der folgenden verwendet werden:

6,0

Ausdruck:

public static string NameOf<T>(Expression<Func<T>> propExp)
{
    var memberExpression = propExp.Body as MemberExpression;
    return memberExpression != null ? memberExpression.Member.Name : null;
}

public static string NameOf<TObj, T>(Expression<Func<TObj, T>> propExp)
{
    var memberExpression = propExp.Body as MemberExpression;
    return memberExpression != null ? memberExpression.Member.Name : null;
}

Verwendungszweck:

string variableName = NameOf(() => variable);
string propertyName = NameOf((Foo o) => o.Bar);

Beachten Sie, dass bei diesem Ansatz bei jedem Aufruf ein Ausdrucksbaum erstellt wird. nameof ist die Leistung im Vergleich zum Operator nameof , der zur Kompilierzeit ausgewertet wird, deutlich schlechter und hat zur Laufzeit nameof Overhead.

Mitglieder mit Ausdrucksfunktion

Expression-body-Funktionsmitglieder erlauben die Verwendung von Lambda-Ausdrücken als Elementkörper. Bei einfachen Mitgliedern kann dies zu sauberem und lesbarerem Code führen.

Funktionen mit Ausdrucksfunktionen können für Eigenschaften, Indexer, Methoden und Operatoren verwendet werden.


Eigenschaften

public decimal TotalPrice => BasePrice + Taxes;

Ist äquivalent zu:

public decimal TotalPrice
{
    get
    {
        return BasePrice + Taxes;
    }
}

Wenn eine Ausdrucksfunktion mit einer Eigenschaft verwendet wird, wird die Eigenschaft als reine Getter-Eigenschaft implementiert.

Demo anzeigen


Indexer

public object this[string key] => dictionary[key];

Ist äquivalent zu:

public object this[string key]
{
    get
    {
        return dictionary[key];
    }
}

Methoden

static int Multiply(int a, int b) => a * b;

Ist äquivalent zu:

static int Multiply(int a, int b)
{
    return a * b;
}

Welche auch mit void methoden verwendet void können:

public void Dispose() => resource?.Dispose();

Eine Überschreibung von ToString könnte der Pair<T> -Klasse hinzugefügt werden:

public override string ToString() => $"{First}, {Second}";

Darüber hinaus funktioniert dieser vereinfachte Ansatz mit dem override :

public class Foo
{
    public int Bar { get; }

    public string override ToString() => $"Bar: {Bar}";
}

Operatoren

Dies kann auch von Operatoren verwendet werden:

public class Land
{
    public double Area { get; set; }

    public static Land operator +(Land first, Land second) =>
        new Land { Area = first.Area + second.Area };
}

Einschränkungen

Mitglieder mit Ausdrucksfunktion haben einige Einschränkungen. Sie können nicht blockieren Aussagen und andere Aussagen enthalten , die Blöcke enthalten: if , switch , for , foreach , while , do , try , usw.

Einige if Anweisungen können durch ternäre Operatoren ersetzt werden. Einige for und foreach - Anweisungen können auf LINQ - Abfragen umgewandelt werden, zum Beispiel:

IEnumerable<string> Digits
{
    get
    {
        for (int i = 0; i < 10; i++)
            yield return i.ToString();
    }
}
IEnumerable<string> Digits => Enumerable.Range(0, 10).Select(i => i.ToString());

In allen anderen Fällen kann die alte Syntax für Funktionsmitglieder verwendet werden.

Funktionsbesetzte mit Ausdrucksfunktion können async / await , sind jedoch häufig überflüssig:

async Task<int> Foo() => await Bar();  

Kann ersetzt werden durch:

Task<int> Foo() => Bar();

Ausnahmefilter

Ausnahmefilter geben Entwicklern die Möglichkeit, einem catch- Block eine Bedingung (in Form eines boolean Ausdrucks) hinzuzufügen, sodass der catch nur ausgeführt werden kann, wenn die Bedingung als true ausgewertet wird.

Ausnahmefilter ermöglichen die Weitergabe von Debug-Informationen in der ursprünglichen Ausnahme, wobei die if Anweisung innerhalb eines catch Blocks verwendet wird und die Ausnahme erneut ausgelöst wird, um die Weitergabe von Debug-Informationen in der ursprünglichen Ausnahme zu stoppen. Bei Ausnahmefiltern breitet sich die Ausnahme im Aufrufstapel weiter nach oben aus, sofern die Bedingung nicht erfüllt ist. Ausnahmefilter erleichtern das Debugging daher erheblich. Anstatt bei der throw Anweisung anzuhalten, stoppt der Debugger bei der Anweisung, die die Ausnahme auslöst, wobei der aktuelle Status und alle lokalen Variablen erhalten bleiben. Crash Dumps sind auf ähnliche Weise betroffen.

Ausnahmefilter werden von der CLR von Anfang an unterstützt und sind seit über einem Jahrzehnt über VB.NET und F # verfügbar, indem ein Teil des Ausnahmebehandlungsmodells der CLR verfügbar gemacht wird. Erst nach der Veröffentlichung von C # 6.0 war die Funktionalität auch für C # -Entwickler verfügbar.


Ausnahmefilter verwenden

Ausnahmefilter werden verwendet, indem eine when Klausel an den catch Ausdruck catch . Es ist möglich, einen beliebigen Ausdruck zu verwenden, der einen bool in einer when Klausel bool (mit Ausnahme von await ). Auf die deklarierte Exception-Variable ex aus der when Klausel zugegriffen werden:

var SqlErrorToIgnore = 123;
try
{
    DoSQLOperations();
}
catch (SqlException ex) when (ex.Number != SqlErrorToIgnore)
{
    throw new Exception("An error occurred accessing the database", ex);
}

Mehrere catch Blöcke mit when Klauseln können kombiniert werden. Die erste when Klausel, die true zurückgibt, bewirkt, dass die Ausnahme abgefangen wird. Sein catch Block wird eingegeben, während die anderen catch Klauseln ignoriert werden (ihre when Klauseln werden nicht ausgewertet). Zum Beispiel:

try
{ ... }
catch (Exception ex) when (someCondition) //If someCondition evaluates to true,
                                          //the rest of the catches are ignored.
{ ... }
catch (NotImplementedException ex) when (someMethod()) //someMethod() will only run if
                                                       //someCondition evaluates to false
{ ... }
catch(Exception ex) // If both when clauses evaluate to false
{ ... }

Riskante Wannenklausel

Vorsicht

Die Verwendung von Ausnahmefiltern kann riskant sein: Wenn eine Exception aus der when Klausel ausgelöst wird, wird die Exception aus der when Klausel ignoriert und als false behandelt. Dieser Ansatz ermöglicht es Entwicklern zu schreiben , when Klausel ohne Pflege ungültiger Fälle zu nehmen.

Das folgende Beispiel veranschaulicht ein solches Szenario:

public static void Main()
{
    int a = 7;
    int b = 0;
    try
    {
        DoSomethingThatMightFail();
    }
    catch (Exception ex) when (a / b == 0)
    {
        // This block is never reached because a / b throws an ignored
        // DivideByZeroException which is treated as false.
    }
    catch (Exception ex)
    {
        // This block is reached since the DivideByZeroException in the 
        // previous when clause is ignored.
    }
}

public static void DoSomethingThatMightFail()
{
    // This will always throw an ArgumentNullException.
    Type.GetType(null);
}

Demo anzeigen

Beachten Sie, dass Ausnahmefilter die verwirrenden Probleme mit der Zeilennummer vermeiden, die mit der Verwendung von throw wenn fehlerhafter Code sich in derselben Funktion befindet. In diesem Fall wird die Zeilennummer beispielsweise als 6 anstelle von 3 angegeben:

1. int a = 0, b = 0;
2. try {
3.     int c = a / b;
4. }
5. catch (DivideByZeroException) {
6.     throw;
7. }

Die Ausnahmezeilennummer wird als 6 gemeldet, da der Fehler mit der throw Anweisung in Zeile 6 abgefangen und erneut ausgelöst wurde.

Dasselbe passiert nicht mit Ausnahmefiltern:

1. int a = 0, b = 0;
2. try {
3.     int c = a / b;
4. }
5. catch (DivideByZeroException) when (a != 0) {
6.     throw;
7. }

In diesem Beispiel a 0 ist, dann catch wird Klausel ignoriert aber 3 als Zeilennummer angegeben. Dies liegt daran, dass sie den Stapel nicht abwickeln . Insbesondere wird die Ausnahme nicht in Zeile 5 abgefangen , weil a tatsächlich gleich 0 und daher keine Möglichkeit besteht, dass die Ausnahme erneut in Zeile 6 ausgelöst wird, da Zeile 6 nicht ausgeführt wird.


Protokollierung als Nebeneffekt

Methodenaufrufe in der Bedingung können Nebeneffekte verursachen, sodass Ausnahmefilter verwendet werden können, um Code mit Ausnahmen auszuführen, ohne sie zu erfassen. Ein häufiges Beispiel, das dies nutzt, ist eine Log Methode, die immer false zurückgibt. Dadurch können Protokollinformationen während des Debuggens verfolgt werden, ohne dass die Ausnahme erneut ausgelöst werden muss.

Beachten Sie, dass dies zwar eine bequeme Art der Protokollierung zu sein scheint, aber es kann riskant sein, insbesondere wenn Protokollierungsbaugruppen von Drittanbietern verwendet werden. Dies kann zu Ausnahmen führen, wenn in nicht offensichtlichen Situationen protokolliert wird, die möglicherweise nicht leicht erkannt werden (siehe Abschnitt Risikobereitschaft when(...) oben).

try
{
    DoSomethingThatMightFail(s);
}
catch (Exception ex) when (Log(ex, "An error occurred"))
{
    // This catch block will never be reached
}

// ...

static bool Log(Exception ex, string message, params object[] args)
{
    Debug.Print(message, args);
    return false;
}

Demo anzeigen

In früheren Versionen von C # bestand der übliche Ansatz darin, die Ausnahme zu protokollieren und erneut auszulösen.

6,0
try
{
    DoSomethingThatMightFail(s);
}
catch (Exception ex)
{
     Log(ex, "An error occurred");
     throw;
}

// ...

static void Log(Exception ex, string message, params object[] args)
{
    Debug.Print(message, args);
}

Demo anzeigen


Die finally blockieren

Der finally Block wird jedes Mal ausgeführt, ob die Ausnahme ausgelöst wird oder nicht. Eine Feinheit mit Ausdrücken in when wird Ausnahme Filter ausgeführt werden weiter den Stapel vor dem inneren Eingabe finally blockiert. Dies kann zu unerwarteten Ergebnissen und Verhalten führen, wenn Code versucht, den globalen Status (z. B. den Benutzer oder die Kultur des aktuellen Threads) zu ändern und ihn in einen finally Block zurückzusetzen.

Beispiel: finally blockieren

private static bool Flag = false;

static void Main(string[] args)
{
    Console.WriteLine("Start");
    try
    {
        SomeOperation();
    }
    catch (Exception) when (EvaluatesTo())
    {
        Console.WriteLine("Catch");
    }
    finally
    {
        Console.WriteLine("Outer Finally");
    }
}

private static bool EvaluatesTo()
{
    Console.WriteLine($"EvaluatesTo: {Flag}");
    return true;
}

private static void SomeOperation()
{
    try
    {
        Flag = true;
        throw new Exception("Boom");
    }
    finally
    {
        Flag = false;
        Console.WriteLine("Inner Finally");
    }
}

Produzierte Ausgabe:

Start
EvaluatesTo: True
Innerlich zum Schluss
Fang
Äußer Endlich

Demo anzeigen

Wenn im SomeOperation Beispiel die Methode SomeOperation nicht die Änderung der globalen Zustandsänderungen in die when Klauseln des Aufrufers " SomeOperation " möchte, sollte sie auch einen catch Block zum Ändern des Zustands enthalten. Zum Beispiel:

private static void SomeOperation()
{
    try
    {
        Flag = true;
        throw new Exception("Boom");
    }
    catch
    {
       Flag = false;
       throw;
    }
    finally
    {
        Flag = false;
        Console.WriteLine("Inner Finally");
    }
}

Es ist auch üblich, dass IDisposable Helfer-Klassen die Semantik der Verwendung von Blöcken nutzen, um dasselbe Ziel zu erreichen, da IDisposable.Dispose immer aufgerufen wird, bevor eine innerhalb eines using Blocks aufgerufene Ausnahme den Stack sprudelt.

Auto-Property-Initialisierer

Einführung

Eigenschaften können nach dem Schließen mit dem Operator = initialisiert werden } . Die folgende Coordinate zeigt die verfügbaren Optionen zum Initialisieren einer Eigenschaft:

6,0
public class Coordinate
{ 
    public int X { get; set; } = 34; // get or set auto-property with initializer

    public int Y { get; } = 89;      // read-only auto-property with initializer              
}

Accessoren mit unterschiedlicher Sichtbarkeit

Sie können Auto-Eigenschaften initialisieren, deren Accessoren unterschiedlich sichtbar sind. Hier ein Beispiel mit einem geschützten Setter:

    public string Name { get; protected set; } = "Cheeze";

Der Accessor kann auch internal , internal protected oder private .


Schreibgeschützte Eigenschaften

Neben der Flexibilität bei der Sichtbarkeit können Sie auch schreibgeschützte Auto-Eigenschaften initialisieren. Hier ist ein Beispiel:

    public List<string> Ingredients { get; } = 
        new List<string> { "dough", "sauce", "cheese" };

Dieses Beispiel zeigt auch, wie eine Eigenschaft mit einem komplexen Typ initialisiert wird. Außerdem können Auto-Eigenschaften nicht nur schreibgeschützt sein, so dass auch die Initialisierung nur zum Schreiben ausgeschlossen ist.


Alter Stil (vor C # 6.0)

Vor C # 6 erforderte dies viel ausführlicheren Code. Wir haben eine zusätzliche Variable namens Backing-Eigenschaft für die Eigenschaft verwendet, um einen Standardwert anzugeben oder die öffentliche Eigenschaft wie folgt zu initialisieren:

6,0
public class Coordinate
{
    private int _x = 34;
    public int X { get { return _x; } set { _x = value; } }

    private readonly int _y = 89;
    public int Y { get { return _y; } }
    
    private readonly int _z;
    public int Z { get { return _z; } }

    public Coordinate()
    {
        _z = 42;
    }
}

Hinweis: Vor C # 6.0 konnten Sie die automatisch implementierten Eigenschaften Lesen und Schreiben (Eigenschaften mit einem Getter und einem Setter) innerhalb des Konstruktors immer noch initialisieren, die Eigenschaft konnte jedoch nicht mit ihrer Deklaration inline initialisiert werden

Demo anzeigen


Verwendungszweck

Initialisierer müssen statische Ausdrücke auswerten, genau wie Feldinitialisierer. Wenn Sie auf nicht statische Member verweisen müssen, können Sie Eigenschaften in Konstruktoren wie zuvor initialisieren oder Eigenschaften mit Ausdruck verwenden. Nicht statische Ausdrücke wie die unten stehende (auskommentiert) erzeugen einen Compiler-Fehler:

// public decimal X { get; set; } = InitMe();  // generates compiler error

decimal InitMe() { return 4m; }

Statische Methoden können jedoch zum Initialisieren von Auto-Eigenschaften verwendet werden:

public class Rectangle
{
    public double Length { get; set; } = 1;
    public double Width { get; set; } = 1;
    public double Area { get; set; } = CalculateArea(1, 1);

    public static double CalculateArea(double length, double width)
    {
        return length * width;
    }
}

Diese Methode kann auch auf Eigenschaften mit unterschiedlichen Zugriffsstufen angewendet werden:

public short Type { get; private set; } = 15;

Der Auto-Property-Initializer ermöglicht die Zuweisung von Eigenschaften direkt in ihrer Deklaration. Bei schreibgeschützten Eigenschaften werden alle Anforderungen berücksichtigt, um sicherzustellen, dass die Eigenschaft unveränderlich ist. Betrachten Sie beispielsweise die FingerPrint Klasse im folgenden Beispiel:

public class FingerPrint
{
  public DateTime TimeStamp { get; } = DateTime.UtcNow;

  public string User { get; } =
    System.Security.Principal.WindowsPrincipal.Current.Identity.Name;

  public string Process { get; } =
    System.Diagnostics.Process.GetCurrentProcess().ProcessName;
}

Demo anzeigen


Warnhinweise

Achten Sie darauf, Auto-Property- oder Feldinitialisierer nicht mit ähnlich aussehenden Ausdruckskörpermethoden zu verwechseln, die => im Gegensatz zu = , und Felder, die { get; } .

Zum Beispiel sind die folgenden Deklarationen unterschiedlich.

public class UserGroupDto
{
    // Read-only auto-property with initializer:       
    public ICollection<UserDto> Users1 { get; } = new HashSet<UserDto>();
    
    // Read-write field with initializer:
    public ICollection<UserDto> Users2 = new HashSet<UserDto>();

    // Read-only auto-property with expression body:
    public ICollection<UserDto> Users3 => new HashSet<UserDto>();
}

Fehlt { get; } in der Eigenschaftsdeklaration führt zu einem öffentlichen Feld. Sowohl das schreibgeschützte Auto-Eigenschaft- Users1 als auch das Read-Write-Feld Users2 werden nur einmal initialisiert. Ein öffentliches Feld ermöglicht jedoch das Ändern der Erfassungsinstanz von außerhalb der Klasse, was normalerweise unerwünscht ist. Um eine schreibgeschützte Auto-Eigenschaft mit dem Ausdruck body in eine schreibgeschützte Eigenschaft mit Initializer zu ändern, müssen Sie nicht nur > from => entfernen, sondern auch { get; } .

Das andere Symbol ( => anstelle von = ) in Users3 führt dazu, dass bei jedem Zugriff auf die Eigenschaft eine neue Instanz des HashSet<UserDto> die zwar das gültige C # (aus der Sicht des Compilers) wahrscheinlich nicht das gewünschte Verhalten darstellt Wird für ein Sammlungsmitglied verwendet.

Der obige Code entspricht:

public class UserGroupDto
{
    // This is a property returning the same instance
    // which was created when the UserGroupDto was instantiated.
    private ICollection<UserDto> _users1 = new HashSet<UserDto>();
    public ICollection<UserDto> Users1 { get { return _users1; } }

    // This is a field returning the same instance
    // which was created when the UserGroupDto was instantiated.
    public virtual ICollection<UserDto> Users2 = new HashSet<UserDto>();

    // This is a property which returns a new HashSet<UserDto> as
    // an ICollection<UserDto> on each call to it.
    public ICollection<UserDto> Users3 { get { return new HashSet<UserDto>(); } }
}

Index-Initialisierer

Indexinitialisierer ermöglichen das gleichzeitige Erstellen und Initialisieren von Objekten mit Indizes.

Dies macht das Initialisieren von Wörterbüchern sehr einfach:

var dict = new Dictionary<string, int>()
{
    ["foo"] = 34,
    ["bar"] = 42
};

Jedes Objekt mit einem indizierten Getter oder Setter kann mit dieser Syntax verwendet werden:

class Program
{
    public class MyClassWithIndexer
    {
        public int this[string index]
        {
            set
            {
                Console.WriteLine($"Index: {index}, value: {value}");
            }
        }
    }

    public static void Main()
    {
        var x = new MyClassWithIndexer()
        {
            ["foo"] = 34,
            ["bar"] = 42
        };

        Console.ReadKey();
    }
}

Ausgabe:

Index: foo, Wert: 34
Index: Bar, Wert: 42

Demo anzeigen

Wenn die Klasse über mehrere Indexer verfügt, können sie alle in einer einzigen Anweisungsgruppe zugewiesen werden:

class Program
{
    public class MyClassWithIndexer
    {
        public int this[string index]
        {
            set
            {
                Console.WriteLine($"Index: {index}, value: {value}");
            }
        }
        public string this[int index]
        {
            set
            {
                Console.WriteLine($"Index: {index}, value: {value}");
            }
        }
    }

    public static void Main()
    {
        var x = new MyClassWithIndexer()
        {
            ["foo"] = 34,
            ["bar"] = 42,
            [10] = "Ten",
            [42] = "Meaning of life"
        };
    }
}

Ausgabe:

Index: foo, Wert: 34
Index: Bar, Wert: 42
Index: 10, Wert: Zehn
Index: 42, Wert: Sinn des Lebens

Es sollte beachtet werden, dass der Indexer- set Accessor sich möglicherweise anders verhält als eine Add Methode (die in Collection-Initialisierern verwendet wird).

Zum Beispiel:

var d = new Dictionary<string, int>
{
    ["foo"] = 34,
    ["foo"] = 42,
}; // does not throw, second value overwrites the first one

gegen:

var d = new Dictionary<string, int>
{
    { "foo", 34 },
    { "foo", 42 },
}; // run-time ArgumentException: An item with the same key has already been added.

String-Interpolation

Durch die String-Interpolation kann der Entwickler variables und Text zu einem String kombinieren.


Basisbeispiel

Es werden zwei int Variablen erstellt: foo und bar .

int foo = 34;
int bar = 42;

string resultString = $"The foo is {foo}, and the bar is {bar}.";

Console.WriteLine(resultString);

Ausgabe :

Das Foo ist 34 und die Bar ist 42.

Demo anzeigen

Klammern innerhalb von Strings können wie folgt verwendet werden:

var foo = 34;
var bar = 42;

// String interpolation notation (new style)
Console.WriteLine($"The foo is {{foo}}, and the bar is {{bar}}.");

Dies erzeugt die folgende Ausgabe:

Das Foo ist {Foo} und die Bar ist {Bar}.


Verwenden der Interpolation mit wörtlichen String-Literalen

Die Verwendung von @ vor dem String bewirkt, dass der String wörtlich interpretiert wird. So bleiben beispielsweise Unicode-Zeichen oder Zeilenumbrüche genauso wie sie eingegeben wurden. Dies wirkt sich jedoch nicht auf die Ausdrücke in einer interpolierten Zeichenfolge aus, wie im folgenden Beispiel gezeigt:

Console.WriteLine($@"In case it wasn't clear:
\u00B9
The foo
is {foo},
and the bar
is {bar}.");
Ausgabe:

Falls es nicht klar war:
\ u00B9
Das foo
ist 34,
und die Bar
ist 42.

Demo anzeigen


Ausdrücke

Bei der String-Interpolation können auch Ausdrücke in geschweiften Klammern {} ausgewertet werden. Das Ergebnis wird an der entsprechenden Stelle in der Zeichenfolge eingefügt. Um beispielsweise das Maximum von foo und bar zu berechnen und einzufügen, verwenden Sie Math.Max innerhalb der geschweiften Klammern:

Console.WriteLine($"And the greater one is: { Math.Max(foo, bar) }");

Ausgabe:

Und der größere ist: 42

Hinweis: Alle führenden oder nachgestellten Leerzeichen (einschließlich Leerzeichen, Tabulatorzeichen und CRLF / Zeilenumbruch) zwischen der geschweiften Klammer und dem Ausdruck werden vollständig ignoriert und nicht in die Ausgabe aufgenommen

Demo anzeigen

Als weiteres Beispiel können Variablen als Währung formatiert werden:

Console.WriteLine($"Foo formatted as a currency to 4 decimal places: {foo:c4}");

Ausgabe:

Foo als Währung mit 4 Dezimalstellen formatiert: 34,0000 USD

Demo anzeigen

Oder sie können als Datumsangaben formatiert werden:

Console.WriteLine($"Today is: {DateTime.Today:dddd, MMMM dd - yyyy}");

Ausgabe:

Heute ist: Montag, 20. Juli - 2015

Demo anzeigen

Anweisungen mit einem bedingten (ternären) Operator können auch innerhalb der Interpolation ausgewertet werden. Diese müssen jedoch in Klammern eingeschlossen werden, da der Doppelpunkt andernfalls verwendet wird, um die Formatierung wie oben gezeigt anzuzeigen:

Console.WriteLine($"{(foo > bar ? "Foo is larger than bar!" : "Bar is larger than foo!")}");

Ausgabe:

Bar ist größer als Foo!

Demo anzeigen

Bedingte Ausdrücke und Formatbezeichner können gemischt werden:

Console.WriteLine($"Environment: {(Environment.Is64BitProcess ? 64 : 32):00'-bit'} process");

Ausgabe:

Umgebung: 32-Bit-Prozess


Escape-Sequenzen

Die umgekehrten Schrägstriche ( \ ) und Anführungszeichen ( " ) werden durchgängig für interpolierte Zeichenfolgen wie für nicht interpolierte Zeichenfolgen verwendet.

Console.WriteLine($"Foo is: {foo}. In a non-verbatim string, we need to escape \" and \\ with backslashes.");
Console.WriteLine($@"Foo is: {foo}. In a verbatim string, we need to escape "" with an extra quote, but we don't need to escape \");

Ausgabe:

Foo ist 34. In einer nicht-wortgetreuen Zeichenfolge müssen wir "und \ mit Backslashes" abbrechen.
Foo ist 34. In einer wörtlichen Zeichenfolge müssen wir mit einem zusätzlichen Anführungszeichen ""

Um eine geschweifte Klammer { oder } in eine interpolierte Zeichenfolge aufzunehmen, verwenden Sie zwei geschweifte Klammern {{ oder }} :

$"{{foo}} is: {foo}"

Ausgabe:

{foo} ist: 34

Demo anzeigen


FormattableString-Typ

Der Typ eines $"..." Zeichenfolgeninterpolationsausdrucks ist nicht immer eine einfache Zeichenfolge. Der Compiler entscheidet je nach Kontext, welchen Typ er zuweisen soll:

string s = $"hello, {name}";
System.FormattableString s = $"Hello, {name}";
System.IFormattable s = $"Hello, {name}";

Dies ist auch die Reihenfolge der Typeinstellung, wenn der Compiler auswählen muss, welche überladene Methode aufgerufen wird.

Ein neuer Typ , System.FormattableString , repräsentiert eine zusammengesetzte System.FormattableString zusammen mit den zu formatierenden Argumenten. Verwenden Sie diese Option, um Anwendungen zu schreiben, die die Interpolationsargumente speziell behandeln:

public void AddLogItem(FormattableString formattableString)
{
    foreach (var arg in formattableString.GetArguments())
    {
        // do something to interpolation argument 'arg'
    }

    // use the standard interpolation and the current culture info
    // to get an ordinary String:
    var formatted = formattableString.ToString();

    // ...
}

Rufen Sie die obige Methode auf mit:

AddLogItem($"The foo is {foo}, and the bar is {bar}.");
Zum Beispiel könnte man sich entscheiden, die Leistungskosten für die Formatierung der Zeichenfolge nicht zu tragen, wenn die Protokollierungsstufe das Protokollelement bereits herausfiltert.

Implizite Konvertierungen

Es gibt implizite Typkonvertierungen aus einer interpolierten Zeichenfolge:

var s = $"Foo: {foo}";
System.IFormattable s = $"Foo: {foo}";
Sie können auch eine IFormattable Variable IFormattable , mit der Sie die Zeichenfolge mit einem unveränderlichen Kontext konvertieren können:
var s = $"Bar: {bar}";
System.FormattableString s = $"Bar: {bar}";

Aktuelle und invariante Kulturmethoden

Wenn die Codeanalyse aktiviert ist, wird bei allen interpolierten Zeichenfolgen die Warnung CA1305 ( IFormatProvider ) IFormatProvider . Eine statische Methode kann verwendet werden, um die aktuelle Kultur anzuwenden.

public static class Culture
{
    public static string Current(FormattableString formattableString)
    {
        return formattableString?.ToString(CultureInfo.CurrentCulture);
    }
    public static string Invariant(FormattableString formattableString)
    {
        return formattableString?.ToString(CultureInfo.InvariantCulture);
    }
}

Um eine korrekte Zeichenfolge für die aktuelle Kultur zu erstellen, verwenden Sie einfach den Ausdruck:

Culture.Current($"interpolated {typeof(string).Name} string.")
Culture.Invariant($"interpolated {typeof(string).Name} string.")
Hinweis : Current und Invariant können nicht als Erweiterungsmethoden erstellt werden, da der Compiler dem interpolierten Zeichenfolgenausdruck standardmäßig den Typ String zuweist, wodurch der folgende Code nicht kompiliert werden kann:

$"interpolated {typeof(string).Name} string.".Current();

FormattableString Klasse enthält bereits die Invariant() -Methode. Die einfachste Möglichkeit, zur invarianten Kultur zu wechseln, ist die using static :

using static System.FormattableString;

string invariant = Invariant($"Now = {DateTime.Now}"); string current = $"Now = {DateTime.Now}";


Hinter den Kulissen

Interpolierte Zeichenfolgen sind nur ein syntaktischer Zucker für String.Format() . Der Compiler ( Roslyn ) verwandelt es hinter den Kulissen in ein String.Format :

var text = $"Hello {name + lastName}";

Das obige wird in etwa so konvertiert:

string text = string.Format("Hello {0}", new object[] {
    name + lastName
});

String Interpolation und Linq

In Linq-Anweisungen können interpolierte Zeichenfolgen verwendet werden, um die Lesbarkeit weiter zu verbessern.

var fooBar = (from DataRow x in fooBarTable.Rows
          select string.Format("{0}{1}", x["foo"], x["bar"])).ToList();

Kann umgeschrieben werden als:

var fooBar = (from DataRow x in fooBarTable.Rows
          select $"{x["foo"]}{x["bar"]}").ToList();

Wiederverwendbare interpolierte Zeichenketten

Mit string.Format können Sie wiederverwendbare string.Format erstellen:

public const string ErrorFormat = "Exception caught:\r\n{0}";

// ...

Logger.Log(string.Format(ErrorFormat, ex));

Interpolierte Zeichenfolgen werden jedoch nicht mit Platzhaltern kompiliert, die sich auf nicht vorhandene Variablen beziehen. Folgendes wird nicht kompiliert:

public const string ErrorFormat = $"Exception caught:\r\n{error}";
// CS0103: The name 'error' does not exist in the current context

Erstellen Func<> stattdessen eine Func<> die Variablen verwendet und einen String zurückgibt:

public static Func<Exception, string> FormatError =
    error => $"Exception caught:\r\n{error}";

// ...

Logger.Log(FormatError(ex));

String Interpolation und Lokalisierung

Wenn Sie Ihre Anwendung lokalisieren, fragen Sie sich möglicherweise, ob Sie die String-Interpolation zusammen mit der Lokalisierung verwenden können. Tatsächlich wäre es schön, die Möglichkeit , in Ressource zu speichern, zu müssen Dateien String s wie:

"My name is {name} {middlename} {surname}"
statt der viel weniger lesbaren:

"My name is {0} {1} {2}"

String Interpolationsprozess findet zur Kompilierungszeit statt , im Gegensatz zur Formatierungszeichenfolge mit string.Format die zur Laufzeit erfolgt . Ausdrücke in einer interpolierten Zeichenfolge müssen auf Namen im aktuellen Kontext verweisen und müssen in Ressourcendateien gespeichert werden. Das bedeutet, wenn Sie die Lokalisierung verwenden möchten, müssen Sie Folgendes tun:

var FirstName = "John";

// method using different resource file "strings"
// for French ("strings.fr.resx"), German ("strings.de.resx"), 
// and English ("strings.en.resx")
void ShowMyNameLocalized(string name, string middlename = "", string surname = "")
{
    // get localized string
    var localizedMyNameIs = Properties.strings.Hello;
    // insert spaces where necessary
    name = (string.IsNullOrWhiteSpace(name) ? "" : name + " ");
    middlename = (string.IsNullOrWhiteSpace(middlename) ? "" : middlename + " ");
    surname = (string.IsNullOrWhiteSpace(surname) ? "" : surname + " ");
    // display it
    Console.WriteLine($"{localizedMyNameIs} {name}{middlename}{surname}".Trim());
}

// switch to French and greet John
Thread.CurrentThread.CurrentUICulture = CultureInfo.GetCultureInfo("fr-FR");
ShowMyNameLocalized(FirstName);

// switch to German and greet John
Thread.CurrentThread.CurrentUICulture = CultureInfo.GetCultureInfo("de-DE");
ShowMyNameLocalized(FirstName);

// switch to US English and greet John
Thread.CurrentThread.CurrentUICulture = CultureInfo.GetCultureInfo("en-US");
ShowMyNameLocalized(FirstName);

Wenn die Ressourcenzeichenfolgen für die oben verwendeten Sprachen korrekt in den einzelnen Ressourcendateien gespeichert sind, sollten Sie die folgende Ausgabe erhalten:

Bonjour, mon nom est John
Hallo, mein Name ist John
Hallo mein Name ist John

Beachten Sie, dass dies bedeutet, dass der Name in jeder Sprache der lokalisierten Zeichenfolge folgt. Ist dies nicht der Fall, müssen Sie den Ressourcenzeichenfolgen Platzhalter hinzufügen und die obige Funktion ändern oder die Kulturinformationen in der Funktion abfragen und eine switch case-Anweisung angeben, die die verschiedenen Fälle enthält. Weitere Informationen zu Ressourcendateien finden Sie unter So verwenden Sie die Lokalisierung in C # .

Es empfiehlt sich, eine Standard-Ausweichsprache zu verwenden, die die meisten Leute verstehen, falls keine Übersetzung verfügbar ist. Ich schlage vor, Englisch als Standard-Ausweichsprache zu verwenden.

Rekursive Interpolation

Obwohl es nicht sehr nützlich ist, darf eine interpolierte string rekursiv in den geschweiften Klammern eines anderen verwendet werden:

Console.WriteLine($"String has {$"My class is called {nameof(MyClass)}.".Length} chars:");
Console.WriteLine($"My class is called {nameof(MyClass)}.");

Ausgabe:

String hat 27 Zeichen:

Meine Klasse heißt MyClass.

Erwarte den Fang und endlich

Es ist möglich , die Verwendung await Ausdruck anzuwenden erwarten Betreiber zu Aufgaben oder Aufgaben (Of TResult) im catch und finally Blöcken in C # 6.

Es war nicht möglich , die verwenden await Ausdruck in dem catch und finally Blöcke in früheren Versionen aufgrund Compiler Einschränkungen. C # 6 macht viel einfacher async Aufgaben warten durch die damit await Ausdruck.

try
{
    //since C#5
    await service.InitializeAsync();
} 
catch (Exception e)
{
    //since C#6
    await logger.LogAsync(e);
}
finally
{
    //since C#6
    await service.CloseAsync();
}

In C # 5 war es erforderlich, einen bool oder eine Exception außerhalb des Try bool deklarieren, um asynchrone Operationen auszuführen. Diese Methode wird im folgenden Beispiel gezeigt:

bool error = false;
Exception ex = null;

try
{
    // Since C#5
    await service.InitializeAsync();
} 
catch (Exception e)
{
    // Declare bool or place exception inside variable
    error = true;
    ex = e;
}

// If you don't use the exception
if (error)
{
    // Handle async task
}

// If want to use information from the exception
if (ex != null)
{
    await logger.LogAsync(e);
}    

// Close the service, since this isn't possible in the finally
await service.CloseAsync();

Null Ausbreitung

Die ?. Operator und Operator ?[...] werden als nullbedingter Operator bezeichnet . Es wird manchmal auch mit anderen Namen wie dem sicheren Navigationsoperator bezeichnet .

Das ist nützlich, denn wenn . Der Operator (member accessor) wird auf einen Ausdruck angewendet, der den NullReferenceException null NullReferenceException . Das Programm NullReferenceException eine NullReferenceException . Wenn der Entwickler stattdessen das ?. Verwendet ?. (nullbedingter) Operator: Der Ausdruck wird zu null ausgewertet, anstatt eine Ausnahme auszulösen.

Beachten Sie, dass wenn ?. Operator wird verwendet und der Ausdruck ist nicht null, ?. und . sind gleichwertig.


Grundlagen

var teacherName = classroom.GetTeacher().Name;
// throws NullReferenceException if GetTeacher() returns null

Demo anzeigen

Wenn das classroom keinen Lehrer hat, gibt GetTeacher() möglicherweise null . Wenn der NullReferenceException null und auf die Name Eigenschaft zugegriffen wird, wird eine NullReferenceException ausgelöst.

Wenn wir diese Aussage ändern, um das ?. Syntax, das Ergebnis des gesamten Ausdrucks ist null :

var teacherName = classroom.GetTeacher()?.Name;
// teacherName is null if GetTeacher() returns null

Demo anzeigen

Wenn classroom auch null kann, können Sie diese Anweisung auch folgendermaßen schreiben:

var teacherName = classroom?.GetTeacher()?.Name;
// teacherName is null if GetTeacher() returns null OR classroom is null

Demo anzeigen

Dies ist ein Beispiel für einen Kurzschluss: Wenn eine bedingte Zugriffsoperation, die den nullbedingten Operator verwendet, zu Null ausgewertet wird, wird der gesamte Ausdruck sofort zu Null ausgewertet, ohne den Rest der Kette zu verarbeiten.

Wenn das Terminal-Member eines Ausdrucks, der den Operator mit dem Wert null enthält, einen Nullable<T> , wird der Ausdruck zu einem Nullable<T> dieses Typs ausgewertet und kann daher nicht als direkter Ersatz für den Ausdruck ohne ?. Verwendet werden ?. .

bool hasCertification = classroom.GetTeacher().HasCertification;
// compiles without error but may throw a NullReferenceException at runtime

bool hasCertification = classroom?.GetTeacher()?.HasCertification;
// compile time error: implicit conversion from bool? to bool not allowed

bool? hasCertification = classroom?.GetTeacher()?.HasCertification;
// works just fine, hasCertification will be null if any part of the chain is null

bool hasCertification = classroom?.GetTeacher()?.HasCertification.GetValueOrDefault();
// must extract value from nullable to assign to a value type variable

Verwendung mit dem Null-Koaleszenz-Operator (??)

Sie können den nullbedingten Operator mit dem nullkoaleszierenden Operator ( ?? ) kombinieren, um einen Standardwert zurückzugeben, wenn der Ausdruck in null . Verwenden Sie unser Beispiel oben:

var teacherName = classroom?.GetTeacher()?.Name ?? "No Name";
// teacherName will be "No Name" when GetTeacher() 
// returns null OR classroom is null OR Name is null

Verwenden Sie mit Indexern

Der nullbedingte Operator kann mit Indexern verwendet werden :

var firstStudentName = classroom?.Students?[0]?.Name;

Im obigen Beispiel:

  • Der erste ?. stellt sicher, dass das classroom nicht null .
  • Die zweite ? stellt sicher, dass die gesamte Students Sammlung nicht null .
  • Der dritte ?. Danach stellt der Indexer sicher, dass der [0] Indexer kein null . Es ist zu beachten, dass dieser Vorgang immer noch eine IndexOutOfRangeException .

Verwenden Sie mit leeren Funktionen

Der bedingungslose Operator kann auch mit void Funktionen verwendet werden. In diesem Fall wird die Anweisung jedoch nicht auf null ausgewertet. Es wird nur eine NullReferenceException verhindert.

List<string> list = null;
list?.Add("hi");          // Does not evaluate to null

Verwendung mit Ereignisaufruf

Nehmen Sie die folgende Ereignisdefinition an:

private event EventArgs OnCompleted;

Wenn Sie ein Ereignis aufrufen, ist es üblich, zu überprüfen, ob das Ereignis null wenn keine Abonnenten vorhanden sind:

var handler = OnCompleted;
if (handler != null)
{
    handler(EventArgs.Empty);
}

Da der nullbedingte Operator eingeführt wurde, kann der Aufruf auf eine einzige Zeile reduziert werden:

OnCompleted?.Invoke(EventArgs.Empty);

Einschränkungen

Ein nullbedingter Operator erzeugt einen r-Wert, nicht einen l-Wert. Das heißt, er kann nicht für die Zuweisung von Eigenschaften, für die Ereignisabonnementierung usw. verwendet werden.

// Error: The left-hand side of an assignment must be a variable, property or indexer
Process.GetProcessById(1337)?.EnableRaisingEvents = true;
// Error: The event can only appear on the left hand side of += or -=
Process.GetProcessById(1337)?.Exited += OnProcessExited;

Gotchas

Beachten Sie, dass:

int? nameLength = person?.Name.Length;    // safe if 'person' is null

ist nicht das Gleiche wie:

int? nameLength = (person?.Name).Length;  // avoid this

denn der erstere entspricht:

int? nameLength = person != null ? (int?)person.Name.Length : null;

und letztere entspricht:

int? nameLength = (person != null ? person.Name : null).Length;

Trotz des ternären Operators ?: Wird hier der Unterschied zwischen zwei Fällen erläutert. Diese Operatoren sind nicht gleichwertig. Dies kann mit dem folgenden Beispiel leicht demonstriert werden:

void Main()
{
    var foo = new Foo();
    Console.WriteLine("Null propagation");
    Console.WriteLine(foo.Bar?.Length);

    Console.WriteLine("Ternary");
    Console.WriteLine(foo.Bar != null ? foo.Bar.Length : (int?)null);
}

class Foo
{
    public string Bar
    {
        get
        {
            Console.WriteLine("I was read");
            return string.Empty;
        }
    }
}

Welche Ausgänge:

Null Ausbreitung
ich wurde gelesen
0
Ternär
ich wurde gelesen
ich wurde gelesen
0

Demo anzeigen

Um mehrere Aufrufe zu vermeiden, wäre das Äquivalent folgendes:

var interimResult = foo.Bar;
Console.WriteLine(interimResult != null ? interimResult.Length : (int?)null);

Dieser Unterschied erklärt etwas, warum der Null-Ausbreitungsoperator in Ausdrucksbäumen noch nicht unterstützt wird .

Verwenden Sie statischen Typ

Die using static [Namespace.Type] Anweisung using static [Namespace.Type] ermöglicht das Importieren statischer Member von Typen und Aufzählungswerten. Erweiterungsmethoden werden als Erweiterungsmethoden (nur von einem Typ) importiert, nicht in den Bereich der obersten Ebene.

6,0
using static System.Console;
using static System.ConsoleColor;
using static System.Math;

class Program
{
    static void Main()
    {
        BackgroundColor = DarkBlue;
        WriteLine(Sqrt(2));
    }
}

Live-Demo-Geige

6,0
using System;

class Program
{
    static void Main()
    {
        Console.BackgroundColor = ConsoleColor.DarkBlue;
        Console.WriteLine(Math.Sqrt(2));
    }
}

Verbesserte Überlastauflösung

Der folgende Ausschnitt zeigt ein Beispiel für das Übergeben einer Methodengruppe (im Gegensatz zu einem Lambda), wenn ein Delegat erwartet wird. Überlastauflösung löst dieses Problem jetzt auf, anstatt einen mehrdeutigen Überlastungsfehler aufgrund der Fähigkeit von C # 6 zu verursachen, den Rückgabetyp der übergebenen Methode zu überprüfen.

using System;
public class Program
{
    public static void Main()
    {
        Overloaded(DoSomething);
    }

    static void Overloaded(Action action)
    {
       Console.WriteLine("overload with action called");
    }

    static void Overloaded(Func<int> function)
    {
       Console.WriteLine("overload with Func<int> called");
    }

    static int DoSomething()
    {
        Console.WriteLine(0);
        return 0;
    }
}

Ergebnisse:

6,0

Ausgabe

Überladung mit Func <int> aufgerufen

Demo anzeigen

5,0

Error

Fehler CS0121: Der Aufruf ist zwischen den folgenden Methoden oder Eigenschaften mehrdeutig: 'Program.Overloaded (System.Action)' und 'Program.Overloaded (System.Func)'

C # 6 kann auch den folgenden exakten Übereinstimmungsfall für Lambda-Ausdrücke gut verarbeiten, der zu einem Fehler in C # 5 geführt hätte .

using System;

class Program
{
    static void Foo(Func<Func<long>> func) {}
    static void Foo(Func<Func<int>> func) {}

    static void Main()
    {
        Foo(() => () => 7);
    }
}

Kleinere Änderungen und Bugfixes

Klammern sind jetzt um benannte Parameter verboten. Folgendes wird in C # 5 kompiliert, jedoch nicht in C # 6

5,0
Console.WriteLine((value: 23));

Operanden von is und as dürfen keine Methodengruppen mehr sein. Folgendes wird in C # 5 kompiliert, jedoch nicht in C # 6

5,0
var result = "".Any is byte;

Der native Compiler erlaubte dies (obwohl eine Warnung 1.Any is string und IDisposable.Dispose is object nicht einmal die Kompatibilität der Erweiterungsmethoden, wodurch verrückte Dinge wie 1.Any is string oder IDisposable.Dispose is object .

In dieser Referenz finden Sie Aktualisierungen zu Änderungen.

Verwenden einer Erweiterungsmethode für die Initialisierung der Sammlung

Die Initialisierungssyntax für IEnumerable kann verwendet werden, wenn eine Klasse instanziiert wird, die IEnumerable implementiert und über eine Methode namens Add die einen einzelnen Parameter verwendet.

In früheren Versionen musste diese Add Methode eine Instanzmethode für die zu initialisierende Klasse sein. In C # 6 kann es auch eine Erweiterungsmethode sein.

public class CollectionWithAdd : IEnumerable
{
    public void Add<T>(T item)
    {
        Console.WriteLine("Item added with instance add method: " + item);
    }

    public IEnumerator GetEnumerator()
    {
        // Some implementation here
    }
}

public class CollectionWithoutAdd : IEnumerable
{
    public IEnumerator GetEnumerator()
    {
        // Some implementation here
    }
}

public static class Extensions
{
    public static void Add<T>(this CollectionWithoutAdd collection, T item)
    {
        Console.WriteLine("Item added with extension add method: " + item);
    }
}

public class Program
{
    public static void Main()
    {
        var collection1 = new CollectionWithAdd{1,2,3}; // Valid in all C# versions
        var collection2 = new CollectionWithoutAdd{4,5,6}; // Valid only since C# 6
    }
}

Dies wird ausgegeben:

Element hinzugefügt mit Instanz hinzufügen: 1
Element hinzugefügt mit Instanz hinzufügen: 2
Element hinzugefügt mit Instanz hinzufügen: 3
Element hinzugefügt mit Erweiterungsmethode: 4
Element hinzugefügt mit Erweiterungsmethode: 5
Element hinzugefügt mit Erweiterungsmethode: 6

Deaktivieren Sie Warnungen-Verbesserungen

In C # 5.0 und früheren Versionen konnte der Entwickler Warnungen nur nach Nummer unterdrücken. Mit der Einführung der Roslyn Analyzer benötigt C # eine Möglichkeit, Warnungen, die von bestimmten Bibliotheken ausgegeben werden, zu deaktivieren. Mit C # 6.0 kann die Pragma-Direktive Warnungen anhand des Namens unterdrücken.

Vor:

#pragma warning disable 0501

C # 6.0:

#pragma warning disable CS0501


Modified text is an extract of the original Stack Overflow Documentation
Lizenziert unter CC BY-SA 3.0
Nicht angeschlossen an Stack Overflow