C# Language
C # 6.0-Funktionen
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:
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.
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 derwhen
Klausel ausgelöst wird, wird dieException
aus derwhen
Klausel ignoriert und alsfalse
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);
}
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;
}
In früheren Versionen von C # bestand der übliche Ansatz darin, die Ausnahme zu protokollieren und erneut auszulösen.
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);
}
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
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:
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:
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
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;
}
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
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.
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.
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
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
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
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!
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
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
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
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
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 dasclassroom
nichtnull
. - Die zweite
?
stellt sicher, dass die gesamteStudents
Sammlung nichtnull
. - Der dritte
?.
Danach stellt der Indexer sicher, dass der[0]
Indexer keinnull
. Es ist zu beachten, dass dieser Vorgang immer noch eineIndexOutOfRangeException
.
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
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.
using static System.Console;
using static System.ConsoleColor;
using static System.Math;
class Program
{
static void Main()
{
BackgroundColor = DarkBlue;
WriteLine(Sqrt(2));
}
}
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:
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
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
var result = "".Any is byte;
Der native Compiler erlaubte dies (obwohl eine Warnung
1.Any is string
undIDisposable.Dispose is object
nicht einmal die Kompatibilität der Erweiterungsmethoden, wodurch verrückte Dinge wie1.Any is string
oderIDisposable.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