C# Language
C # 6.0 Kenmerken
Zoeken…
Invoering
Deze zesde iteratie van de C # -taal wordt verzorgd door de Roslyn-compiler. Deze compiler kwam uit met versie 4.6 van het .NET Framework, maar het kan code op een achterwaarts compatibele manier genereren om eerdere versies van het framework te kunnen targeten. C # versie 6 code kan op een volledig achterwaarts compatibele manier worden gecompileerd naar .NET 4.0. Het kan ook worden gebruikt voor eerdere frameworks, maar sommige functies waarvoor extra framework-ondersteuning nodig is, werken mogelijk niet correct.
Opmerkingen
De zesde versie van C # werd uitgebracht in juli 2015 naast Visual Studio 2015 en .NET 4.6.
Naast het toevoegen van enkele nieuwe taalfuncties bevat het een complete herschrijving van de compiler. Voorheen was csc.exe
een native Win32-applicatie geschreven in C ++, met C # 6 is het nu een .NET beheerde applicatie geschreven in C #. Deze herschrijving stond bekend als project "Roslyn" en de code is nu open source en beschikbaar op GitHub .
Naam operator van
De operator nameof
retourneert de naam van een code-element als een string
. Dit is handig bij het gooien van uitzonderingen met betrekking tot INotifyPropertyChanged
en ook bij het implementeren van INotifyPropertyChanged
.
public string SayHello(string greeted)
{
if (greeted == null)
throw new ArgumentNullException(nameof(greeted));
Console.WriteLine("Hello, " + greeted);
}
De operator nameof
wordt tijdens het compileren geëvalueerd en verandert de uitdrukking in een letterlijke tekenreeks. Dit is ook handig voor tekenreeksen die zijn vernoemd naar hun lid dat ze blootlegt. Stel je de volgende situatie voor:
public static class Strings
{
public const string Foo = nameof(Foo); // Rather than Foo = "Foo"
public const string Bar = nameof(Bar); // Rather than Bar = "Bar"
}
Aangezien nameof
expressies zijn compile-time constanten, kunnen ze worden gebruikt in attributen, case
labels, switch
verklaringen, en ga zo maar door.
Het is handig om nameof
te gebruiken met Enum
s. In plaats van:
Console.WriteLine(Enum.One.ToString());
het is mogelijk om te gebruiken:
Console.WriteLine(nameof(Enum.One))
In beide gevallen is de uitvoer One
.
De operator nameof
toegang tot niet-statische leden met behulp van statische syntaxis. In plaats van te doen:
string foo = "Foo";
string lengthName = nameof(foo.Length);
Kan worden vervangen door:
string lengthName = nameof(string.Length);
De uitvoer is in beide voorbeelden Length
. Dit laatste voorkomt echter dat er geen onnodige instanties worden gemaakt.
Hoewel de operator nameof
met de meeste taalconstructies werkt, zijn er enkele beperkingen. U kunt bijvoorbeeld de operator nameof
gebruiken voor open generieke typen of retourwaarden voor methoden:
public static int Main()
{
Console.WriteLine(nameof(List<>)); // Compile-time error
Console.WriteLine(nameof(Main())); // Compile-time error
}
Als u het op een generiek type toepast, wordt bovendien de parameter generiek type genegeerd:
Console.WriteLine(nameof(List<int>)); // "List"
Console.WriteLine(nameof(List<bool>)); // "List"
Zie dit onderwerp gewijd aan nameof
voor meer voorbeelden.
Tijdelijke oplossing voor vorige versies ( meer detail )
Hoewel de operator nameof
niet bestaat in C # voor versies nameof
dan 6.0, kan vergelijkbare functionaliteit worden verkregen door MemberExpression
zoals in de volgende:
Uitdrukking:
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;
}
Gebruik:
string variableName = NameOf(() => variable);
string propertyName = NameOf((Foo o) => o.Bar);
Merk op dat deze aanpak ervoor zorgt dat er bij elke aanroep een expressieboom wordt gemaakt, dus de prestaties zijn veel slechter in vergelijking met nameof
operator die tijdens het compileren wordt geëvalueerd en tijdens runtime nul overhead heeft.
Expressie-gebouwde functieleden
Expressie-gebouwde functieleden staan het gebruik van lambda-uitdrukkingen toe als lidlichamen. Voor eenvoudige leden kan het resulteren in schonere en beter leesbare code.
Expressie-gebaseerde functies kunnen worden gebruikt voor eigenschappen, indexers, methoden en operatoren.
Eigendommen
public decimal TotalPrice => BasePrice + Taxes;
Is gelijk aan:
public decimal TotalPrice
{
get
{
return BasePrice + Taxes;
}
}
Wanneer een expression-bodied-functie wordt gebruikt met een eigenschap, wordt de eigenschap geïmplementeerd als een eigenschap alleen-getter.
indexers
public object this[string key] => dictionary[key];
Is gelijk aan:
public object this[string key]
{
get
{
return dictionary[key];
}
}
methoden
static int Multiply(int a, int b) => a * b;
Is gelijk aan:
static int Multiply(int a, int b)
{
return a * b;
}
Die ook kan worden gebruikt met void
methoden:
public void Dispose() => resource?.Dispose();
Een overschrijving van ToString
kan worden toegevoegd aan de klasse Pair<T>
:
public override string ToString() => $"{First}, {Second}";
Bovendien werkt deze simplistische aanpak met het trefwoord override
:
public class Foo
{
public int Bar { get; }
public string override ToString() => $"Bar: {Bar}";
}
operators
Dit kan ook worden gebruikt door operators:
public class Land
{
public double Area { get; set; }
public static Land operator +(Land first, Land second) =>
new Land { Area = first.Area + second.Area };
}
beperkingen
Expressie-gebouwde functieleden hebben enkele beperkingen. Ze kunnen geen blokafschriften en andere afschriften bevatten die blokken bevatten: if
, switch
, for
, foreach
, while
, do
, try
, etc.
Sommige if
verklaringen kunnen worden vervangen door ternaire operatoren. Sommige for
en foreach
statements kunnen worden omgezet in LINQ query's, bijvoorbeeld:
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 alle andere gevallen kan de oude syntaxis voor functieleden worden gebruikt.
Expression-body-functie-leden kunnen async
/ await
bevatten, maar het is vaak overbodig:
async Task<int> Foo() => await Bar();
Kan worden vervangen door:
Task<int> Foo() => Bar();
Uitzonderingsfilters
Uitzonderingsfilters geven ontwikkelaars de mogelijkheid om een voorwaarde (in de vorm van een boolean
uitdrukking) aan een vangblok toe te voegen, waardoor de catch
alleen kan worden uitgevoerd als de voorwaarde true
.
Uitzonderingsfilters maken de verspreiding van foutopsporingsinformatie in de oorspronkelijke uitzondering mogelijk, terwijl het gebruik van een if
instructie in een catch
blok en het opnieuw gooien van de uitzondering de verspreiding van foutopsporingsinformatie in de oorspronkelijke uitzondering stopt. Met uitzonderingsfilters blijft de uitzondering zich naar boven in de call-stack voortplanten, tenzij aan de voorwaarde is voldaan. Als gevolg hiervan maken uitzonderingsfilters de foutopsporingservaring veel eenvoudiger. In plaats van te stoppen met de instructie throw
, stopt de debugger met de instructie die de uitzondering gooit, met de huidige status en alle lokale variabelen behouden. Crashdumps worden op dezelfde manier beïnvloed.
Uitzonderingsfilters worden vanaf het begin door de CLR ondersteund en zijn al meer dan tien jaar toegankelijk via VB.NET en F # door een deel van het uitzonderingsafhandelingsmodel van de CLR bloot te leggen. Pas na de release van C # 6.0 is de functionaliteit ook beschikbaar voor C # -ontwikkelaars.
Uitzonderingsfilters gebruiken
Uitzondering filters worden gebruikt door toevoegen van een when
clausule de catch
expressie. Het is mogelijk om elke expressie te gebruiken die een bool
retourneert in een when
clausule (behalve wachten ). De gedeclareerde Exception-variabele ex
is toegankelijk vanuit de when
clausule:
var SqlErrorToIgnore = 123;
try
{
DoSQLOperations();
}
catch (SqlException ex) when (ex.Number != SqlErrorToIgnore)
{
throw new Exception("An error occurred accessing the database", ex);
}
Meerdere catch
met when
clausules kunnen worden gecombineerd. De eerste when
clausule true
terugkeert true
zorgt ervoor dat de uitzondering wordt gevangen. De catch
blok zal worden ingevoerd, terwijl de andere catch
clausules zullen worden genegeerd (hun when
clausules worden niet geëvalueerd). Bijvoorbeeld:
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
{ ... }
Riskant wanneer clausule
Voorzichtigheid
Het kan riskant zijn om uitzonderingsfilters te gebruiken: wanneer een
Exception
wordt gegooid vanuit dewhen
clausule, wordt deException
van dewhen
clausule genegeerd en alsfalse
behandeld. Met deze aanpak kunnen ontwikkelaars schrijvenwhen
clausule zonder zorgen voor ongeldige gevallen.
Het volgende voorbeeld illustreert een dergelijk scenario:
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);
}
Merk op dat uitzonderingsfilters de verwarrende problemen met het regelnummer voorkomen die samenhangen met het gebruik van throw
wanneer de code niet werkt binnen dezelfde functie. In dit geval wordt het regelnummer bijvoorbeeld gerapporteerd als 6 in plaats van 3:
1. int a = 0, b = 0;
2. try {
3. int c = a / b;
4. }
5. catch (DivideByZeroException) {
6. throw;
7. }
Het uitzonderingsregelnummer wordt gerapporteerd als 6 omdat de fout is opgevangen en opnieuw wordt gegooid met de throw
instructie op regel 6.
Hetzelfde gebeurt niet met uitzonderingsfilters:
1. int a = 0, b = 0;
2. try {
3. int c = a / b;
4. }
5. catch (DivideByZeroException) when (a != 0) {
6. throw;
7. }
In dit voorbeeld is a
0 en wordt de catch
clausule genegeerd, maar wordt 3 gerapporteerd als regelnummer. Dit komt omdat ze de stapel niet afwikkelen . Meer in het bijzonder wordt de uitzondering niet gevangen op regel 5 omdat a
in feite gelijk is aan 0
en er dus geen mogelijkheid is om de uitzondering op regel 6 weer te geven omdat regel 6 niet wordt uitgevoerd.
Loggen als bijwerking
Methode-aanroepen in de voorwaarde kunnen bijwerkingen veroorzaken, dus uitzonderingsfilters kunnen worden gebruikt om code op uitzonderingen uit te voeren zonder ze te vangen. Een bekend voorbeeld dat gebruik maakt van deze een Log
werkwijze die altijd retourneert false
. Hiermee kunt u logboekgegevens traceren tijdens het debuggen zonder de uitzondering opnieuw te hoeven opgeven.
Wees ervan bewust dat, hoewel dit lijkt een comfortabele manier van logging zijn, kan het riskant zijn, vooral als 3rd party logging assemblages worden gebruikt. Deze kunnen uitzonderingen veroorzaken bij het inloggen in niet-voor de hand liggende situaties die mogelijk niet gemakkelijk worden gedetecteerd (zie clausule Risky When
when(...)
hierboven).
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;
}
De gebruikelijke aanpak in eerdere versies van C # was het loggen en opnieuw gooien van de uitzondering.
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);
}
Het finally
blok
De finally
blok uitgevoerd telkens of de uitzondering niet wordt gegooid of. Een subtiliteit met uitdrukkingen when
uitzonderingsfilters worden uitgevoerd verderop in de stapel voordat de binnenste blokken finally
worden ingevoerd. Dit kan onverwachte resultaten en gedragingen veroorzaken wanneer code probeert de algemene status (zoals de gebruiker of de cultuur van de huidige thread) te wijzigen en terug te zetten in een finally
blok.
Voorbeeld: finally
blokkeren
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");
}
}
Geproduceerde output:
Begin
Evalueert ook: waar
Innerlijk eindelijk
Vangst
Uiterlijk eindelijk
In het bovenstaande voorbeeld, als de methode SomeOperation
niet wenst te "lekken" de globale staat verandert in beller when
clausules, het moet ook een bevatten catch
blok aan de staat aan te passen. Bijvoorbeeld:
private static void SomeOperation()
{
try
{
Flag = true;
throw new Exception("Boom");
}
catch
{
Flag = false;
throw;
}
finally
{
Flag = false;
Console.WriteLine("Inner Finally");
}
}
Het is ook gebruikelijk om te zien dat IDisposable
hulpklassen de semantiek gebruiken om blokken te gebruiken om hetzelfde doel te bereiken, als IDisposable.Dispose
wordt altijd aangeroepen voordat een uitzondering binnen een using
de stapel opborrelt.
Auto-eigenschap initializers
Invoering
Eigenschappen kunnen worden geïnitialiseerd met de operator =
na het sluiten }
. De onderstaande Coordinate
toont de beschikbare opties voor het initialiseren van een eigenschap:
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
}
Accessors met verschillende zichtbaarheid
U kunt auto-eigenschappen initialiseren die een andere zichtbaarheid hebben op hun accessors. Hier is een voorbeeld met een beveiligde setter:
public string Name { get; protected set; } = "Cheeze";
De accessor kan ook internal
, internal protected
of private
.
Alleen-lezen eigenschappen
Naast flexibiliteit met zichtbaarheid, kunt u ook alleen-lezen auto-eigenschappen initialiseren. Hier is een voorbeeld:
public List<string> Ingredients { get; } =
new List<string> { "dough", "sauce", "cheese" };
Dit voorbeeld laat ook zien hoe een eigenschap met een complex type kan worden geïnitialiseerd. Auto-eigenschappen kunnen ook niet alleen-schrijven zijn, dus dat sluit ook alleen-schrijven initialisatie uit.
Oude stijl (pre C # 6.0)
Vóór C # 6 vereiste dit veel meer uitgebreide code. We gebruikten een extra variabele genaamd backing-eigenschap voor de eigenschap om een standaardwaarde te geven of om de openbare eigenschap te initialiseren zoals hieronder,
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;
}
}
Opmerking: Vóór C # 6.0 kon je nog steeds lezen en schrijven van automatisch geïmplementeerde eigenschappen (eigenschappen met een getter en een setter) vanuit de constructor, maar je kon de eigenschap niet inline initialiseren met zijn verklaring
Gebruik
Initializers moeten evalueren naar statische expressies, net als veldinitializers. Als u naar niet-statische leden moet verwijzen, kunt u eigenschappen in constructors zoals eerder initialiseren, of eigenschappen met expressie-eigenschappen gebruiken. Niet-statische expressies, zoals hieronder (zonder commentaar), genereren een compilerfout:
// public decimal X { get; set; } = InitMe(); // generates compiler error
decimal InitMe() { return 4m; }
Maar statische methoden kunnen worden gebruikt om auto-eigenschappen te initialiseren:
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;
}
}
Deze methode kan ook worden toegepast op eigenschappen met verschillende toegangsniveaus:
public short Type { get; private set; } = 15;
Met de auto-initialisatie-eigenschappen kunnen eigenschappen rechtstreeks in hun aangifte worden toegewezen. Voor alleen-lezen eigenschappen zorgt het voor alle vereisten die nodig zijn om ervoor te zorgen dat de eigenschap onveranderlijk is. Beschouw bijvoorbeeld de klasse FingerPrint
in het volgende voorbeeld:
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;
}
Waarschuwingen
Zorg ervoor dat auto-eigenschappen of veldinitializers niet worden verward met op elkaar lijkende expressie-body-methoden die gebruik maken van =>
in tegenstelling tot =
, en velden die geen { get; }
.
Elk van de volgende verklaringen is bijvoorbeeld verschillend.
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>();
}
Ontbreekt { get; }
in de eigenschapsverklaring resulteert in een openbaar veld. Zowel alleen-lezen auto-eigenschap Users1
als lees-schrijf veld Users2
worden slechts eenmaal geïnitialiseerd, maar een openbaar veld maakt het mogelijk om de verzamelinginstantie van buiten de klasse te veranderen, wat meestal ongewenst is. Het veranderen van een alleen-lezen auto-eigenschap met expressie body naar alleen-lezen eigenschap met initialisatie vereist niet alleen het verwijderen van >
van =>
, maar het toevoegen van { get; }
.
Het verschillende symbool ( =>
plaats van =
) in Users3
resulteert in elke toegang tot de eigenschap die een nieuwe instantie van de HashSet<UserDto>
die, hoewel geldig C # (vanuit het oogpunt van de compiler) waarschijnlijk niet het gewenste gedrag is wanneer gebruikt voor een collectielid.
De bovenstaande code is gelijk aan:
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>(); } }
}
Indexinitializers
Indexinitializers maken het mogelijk om tegelijkertijd objecten met indexen te maken en te initialiseren.
Dit maakt het initialiseren van woordenboeken heel eenvoudig:
var dict = new Dictionary<string, int>()
{
["foo"] = 34,
["bar"] = 42
};
Elk object met een geïndexeerde getter of setter kan met deze syntaxis worden gebruikt:
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();
}
}
Output:
Index: foo, waarde: 34
Index: balk, waarde: 42
Als de klasse meerdere indexers heeft, is het mogelijk om ze allemaal in één groep instructies toe te wijzen:
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"
};
}
}
Output:
Index: foo, waarde: 34
Index: balk, waarde: 42
Index: 10, waarde: tien
Index: 42, waarde: zin van het leven
Opgemerkt moet worden dat de indexer set
accessor zich anders kan gedragen dan een Add
methode (gebruikt in initialisatoren van de collectie).
Bijvoorbeeld:
var d = new Dictionary<string, int>
{
["foo"] = 34,
["foo"] = 42,
}; // does not throw, second value overwrites the first one
versus:
var d = new Dictionary<string, int>
{
{ "foo", 34 },
{ "foo", 42 },
}; // run-time ArgumentException: An item with the same key has already been added.
Stringinterpolatie
Met stringinterpolatie kan de ontwikkelaar variables
en tekst combineren om een string te vormen.
Basis voorbeeld
Er worden twee int
variabelen gemaakt: foo
en bar
.
int foo = 34;
int bar = 42;
string resultString = $"The foo is {foo}, and the bar is {bar}.";
Console.WriteLine(resultString);
Uitgang :
De foo is 34 en de bar is 42.
Accolades binnen strings kunnen nog steeds worden gebruikt, zoals hier:
var foo = 34;
var bar = 42;
// String interpolation notation (new style)
Console.WriteLine($"The foo is {{foo}}, and the bar is {{bar}}.");
Dit levert de volgende uitvoer op:
De foo is {foo} en de balk is {bar}.
Interpolatie gebruiken met letterlijke letterreeksen
Als u @
vóór de tekenreeks gebruikt, wordt de tekenreeks letterlijk geïnterpreteerd. Dus bijvoorbeeld Unicode-tekens of regeleinden blijven precies zoals ze zijn getypt. Dit heeft echter geen effect op de uitdrukkingen in een geïnterpoleerde tekenreeks zoals in het volgende voorbeeld:
Console.WriteLine($@"In case it wasn't clear:
\u00B9
The foo
is {foo},
and the bar
is {bar}.");
Output: Voor het geval het niet duidelijk was:
\ u00B9
De foo
is 34,
en de bar
is 42.
Uitdrukkingen
Met stringinterpolatie kunnen uitdrukkingen tussen accolades {}
ook worden geëvalueerd. Het resultaat wordt ingevoegd op de overeenkomstige locatie binnen de string. Gebruik bijvoorbeeld Math.Max
tussen de accolades om het maximum van foo
en bar
te berekenen en in te voegen:
Console.WriteLine($"And the greater one is: { Math.Max(foo, bar) }");
Output:
En de grootste is: 42
Opmerking: Alle voorloop- of volgspaties (inclusief spatie, tab en CRLF / newline) tussen de accolade en de uitdrukking worden volledig genegeerd en zijn niet opgenomen in de uitvoer
Als een ander voorbeeld kunnen variabelen worden opgemaakt als een valuta:
Console.WriteLine($"Foo formatted as a currency to 4 decimal places: {foo:c4}");
Output:
Foo opgemaakt als een valuta tot 4 decimalen: $ 34,0000
Of ze kunnen worden opgemaakt als datums:
Console.WriteLine($"Today is: {DateTime.Today:dddd, MMMM dd - yyyy}");
Output:
Vandaag is: maandag 20 juli 2015
Statements met een voorwaardelijke operator (Ternary) kunnen ook worden geëvalueerd binnen de interpolatie. Deze moeten echter tussen haakjes worden gewikkeld, omdat de dubbele punt anders wordt gebruikt om opmaak aan te geven, zoals hierboven weergegeven:
Console.WriteLine($"{(foo > bar ? "Foo is larger than bar!" : "Bar is larger than foo!")}");
Output:
Bar is groter dan foo!
Voorwaardelijke uitdrukkingen en opmaakspecificaties kunnen worden gemengd:
Console.WriteLine($"Environment: {(Environment.Is64BitProcess ? 64 : 32):00'-bit'} process");
Output:
Omgeving: 32-bits proces
Escape-reeksen
Het ontsnappen van backslash ( \
) en aanhalingstekens ( "
) werkt precies hetzelfde in geïnterpoleerde tekenreeksen als in niet-geïnterpoleerde tekenreeksen, voor zowel letterlijke als niet-letterlijke tekenreeksliteralen:
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 \");
Output:
Foo is 34. In een niet-letterlijke string moeten we ontsnappen "en \ met backslashes.
Foo is 34. In een woordelijke reeks moeten we ontsnappen "met een extra quote, maar we hoeven niet te ontsnappen \
Gebruik een accolade {
of }
in een geïnterpoleerde string door twee accolades {{
of }}
:
$"{{foo}} is: {foo}"
Output:
{foo} is: 34
FormattableString type
Het type van een $"..."
stringinterpolatie-uitdrukking is niet altijd een eenvoudige string. De compiler bepaalt welk type wordt toegewezen, afhankelijk van de context:
string s = $"hello, {name}";
System.FormattableString s = $"Hello, {name}";
System.IFormattable s = $"Hello, {name}";
Dit is ook de volgorde van de typevoorkeur wanneer de compiler moet kiezen welke overbelaste methode wordt aangeroepen.
Een nieuw type , System.FormattableString
, vertegenwoordigt een samengestelde opmaakstring, samen met de te formatteren argumenten. Gebruik dit om toepassingen te schrijven die specifiek met de interpolatieargumenten omgaan:
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();
// ...
}
Roep de bovenstaande methode op met:
AddLogItem($"The foo is {foo}, and the bar is {bar}.");
Je zou er bijvoorbeeld voor kunnen kiezen om de prestatiekosten van het opmaken van de string niet te maken als het logboekniveau het logboekitem al zou uitfilteren. Impliciete conversies
Er zijn impliciete typeconversies van een geïnterpoleerde tekenreeks:
var s = $"Foo: {foo}";
System.IFormattable s = $"Foo: {foo}";
Je kunt ook een IFormattable
variabele produceren waarmee je de string kunt omzetten met een invariante context: var s = $"Bar: {bar}";
System.FormattableString s = $"Bar: {bar}";
Huidige en invariante cultuurmethoden
Als code-analyse is ingeschakeld, produceren geïnterpoleerde tekenreeksen allemaal waarschuwing CA1305 (specificeer IFormatProvider
). Een statische methode kan worden gebruikt om de huidige cultuur toe te passen.
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);
}
}
Gebruik vervolgens de uitdrukking om een juiste string voor de huidige cultuur te produceren:
Culture.Current($"interpolated {typeof(string).Name} string.")
Culture.Invariant($"interpolated {typeof(string).Name} string.")
Opmerking : Current
en Invariant
kunnen niet als extensiemethoden worden gemaakt, omdat de compiler standaard het type String
toewijst aan de geïnterpoleerde stringuitdrukking waardoor de volgende code niet kan worden gecompileerd: $"interpolated {typeof(string).Name} string.".Current();
FormattableString
klasse FormattableString
bevat al de methode Invariant()
, dus de eenvoudigste manier om over te schakelen naar een invariante cultuur is door te vertrouwen op using static
:
using static System.FormattableString;
string invariant = Invariant($"Now = {DateTime.Now}"); string current = $"Now = {DateTime.Now}";
Achter de schermen
String.Format()
tekenreeksen zijn slechts een syntactische suiker voor String.Format()
. De compiler ( Roslyn ) maakt er een String.Format
achter de schermen van:
var text = $"Hello {name + lastName}";
Het bovenstaande wordt omgezet in iets als dit:
string text = string.Format("Hello {0}", new object[] {
name + lastName
});
Stringinterpolatie en Linq
Het is mogelijk om geïnterpoleerde tekenreeksen in Linq-instructies te gebruiken om de leesbaarheid verder te verbeteren.
var fooBar = (from DataRow x in fooBarTable.Rows
select string.Format("{0}{1}", x["foo"], x["bar"])).ToList();
Kan worden herschreven als:
var fooBar = (from DataRow x in fooBarTable.Rows
select $"{x["foo"]}{x["bar"]}").ToList();
Herbruikbare geïnterpoleerde strings
Met string.Format
kunt u reeksen met herbruikbare string.Format
maken:
public const string ErrorFormat = "Exception caught:\r\n{0}";
// ...
Logger.Log(string.Format(ErrorFormat, ex));
Geïnterpoleerde tekenreeksen zullen echter niet compileren met tijdelijke aanduidingen die verwijzen naar niet-bestaande variabelen. Het volgende wordt niet gecompileerd:
public const string ErrorFormat = $"Exception caught:\r\n{error}";
// CS0103: The name 'error' does not exist in the current context
Maak in plaats daarvan een Func<>
die variabelen verbruikt en een String
retourneert:
public static Func<Exception, string> FormatError =
error => $"Exception caught:\r\n{error}";
// ...
Logger.Log(FormatError(ex));
Stringinterpolatie en lokalisatie
Als u uw toepassing lokaliseert, vraagt u zich misschien af of het mogelijk is om stringinterpolatie te gebruiken in combinatie met lokalisatie. Het zou inderdaad leuk zijn om de mogelijkheid te hebben om in bronbestanden String
s op te slaan zoals:
"My name is {name} {middlename} {surname}"
in plaats van het veel minder leesbare: "My name is {0} {1} {2}"
String
interpolatie proces vindt plaats tijdens het compileren, in tegenstelling opmaak string string.Format
die optreedt tijdens de uitvoering. Uitdrukkingen in een geïnterpoleerde tekenreeks moeten verwijzen naar namen in de huidige context en moeten worden opgeslagen in bronbestanden. Dat betekent dat als je lokalisatie wilt gebruiken, je het moet doen zoals:
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);
Als de bronreeksen voor de hierboven gebruikte talen correct zijn opgeslagen in de afzonderlijke bronbestanden, moet u de volgende uitvoer krijgen:
Bonjour, mon nom est John
Hallo, mijn naam is John
Hallo mijn naam is john
Merk op dat dit betekent dat de naam de gelokaliseerde tekenreeks in elke taal volgt. Als dat niet het geval is, moet u tijdelijke aanduidingen toevoegen aan de resourcereeksen en de functie hierboven wijzigen of moet u de cultuurinformatie in de functie opvragen en een schakelgevalverklaring opgeven met de verschillende cases. Zie Lokalisatie gebruiken in C # voor meer informatie over bronbestanden.
Het is een goede gewoonte om een standaardterugvaltaal te gebruiken die de meeste mensen zullen begrijpen, voor het geval een vertaling niet beschikbaar is. Ik stel voor om Engels als standaardterugvaltaal te gebruiken.
Recursieve interpolatie
Hoewel niet erg handig, is het toegestaan om een geïnterpoleerde string
recursief te gebruiken tussen de accolades van een ander:
Console.WriteLine($"String has {$"My class is called {nameof(MyClass)}.".Length} chars:");
Console.WriteLine($"My class is called {nameof(MyClass)}.");
Output:
String heeft 27 tekens:
Mijn klas heet MyClass.
In afwachting wachten en eindelijk
Het is mogelijk om await
expressie te gebruiken om await-operator toe te passen op Taken of Taak (van TResult) in de catch
en finally
blokken in C # 6.
Het was niet mogelijk om de await
uitdrukking in de catch
en finally
blokken in eerdere versies vanwege compilerbeperkingen. C # 6 merken in afwachting van asynchrone taken een stuk eenvoudiger doordat het await
expressie.
try
{
//since C#5
await service.InitializeAsync();
}
catch (Exception e)
{
//since C#6
await logger.LogAsync(e);
}
finally
{
//since C#6
await service.CloseAsync();
}
Het was vereist in C # 5 om een bool
te gebruiken of een Exception
buiten de try-vangst aan te geven om asynchrone bewerkingen uit te voeren. Deze methode wordt weergegeven in het volgende voorbeeld:
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 propagatie
De ?.
operator en ?[...]
operator worden de null-voorwaardelijke operator genoemd . Het wordt soms ook aangeduid met andere namen, zoals de veilige navigatie-operator .
Dit is handig, want als de .
(lid accessor) -operator wordt toegepast op een uitdrukking die als null
evalueert, het programma zal een NullReferenceException
. Als de ontwikkelaar in plaats daarvan het ?.
(null-voorwaardelijke) operator, wordt de uitdrukking geëvalueerd als nul in plaats van een uitzondering te genereren.
Merk op dat als de ?.
operator wordt gebruikt en de uitdrukking is niet-nul, ?.
en .
zijn gelijkwaardig.
Basics
var teacherName = classroom.GetTeacher().Name;
// throws NullReferenceException if GetTeacher() returns null
Als het classroom
geen leraar heeft, kan GetTeacher()
null
retourneren. Wanneer het null
en de eigenschap Name
wordt geopend, wordt een NullReferenceException
gegenereerd.
Als we deze verklaring wijzigen om het ?.
syntaxis, het resultaat van de gehele uitdrukking is null
:
var teacherName = classroom.GetTeacher()?.Name;
// teacherName is null if GetTeacher() returns null
Als het classroom
ook null
zou kunnen zijn, zouden we deze verklaring ook kunnen schrijven als:
var teacherName = classroom?.GetTeacher()?.Name;
// teacherName is null if GetTeacher() returns null OR classroom is null
Dit is een voorbeeld van kortsluiting: wanneer een bewerking met voorwaardelijke toegang met de operator null-voorwaardelijk naar nul evalueert, wordt de gehele expressie onmiddellijk naar nul geëvalueerd, zonder de rest van de keten te verwerken.
Wanneer het terminale lid van een uitdrukking die de null-voorwaardelijke operator bevat van een waardetype is, Nullable<T>
de uitdrukking in een Nullable<T>
van dat type en kan dus niet worden gebruikt als een directe vervanging voor de uitdrukking zonder ?.
.
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
Gebruik met de nul-coalescerende operator (??)
U kunt de null-voorwaardelijke operator combineren met de Null-coalescing-operator ( ??
) om een standaardwaarde te retourneren als de uitdrukking wordt omgezet in null
. Met behulp van ons voorbeeld hierboven:
var teacherName = classroom?.GetTeacher()?.Name ?? "No Name";
// teacherName will be "No Name" when GetTeacher()
// returns null OR classroom is null OR Name is null
Gebruik met Indexers
De null-voorwaardelijke operator kan worden gebruikt met indexers :
var firstStudentName = classroom?.Students?[0]?.Name;
In het bovenstaande voorbeeld:
- De eerste
?.
zorgt ervoor dat hetclassroom
nietnull
. - De tweede
?
zorgt ervoor dat de hele verzamelingStudents
nietnull
. - De derde
?.
nadat de indexer ervoor zorgt dat de[0]
indexer geennull
object heeft geretourneerd. Opgemerkt moet worden dat deze bewerking nog steeds eenIndexOutOfRangeException
kanIndexOutOfRangeException
.
Gebruik met ongeldige functies
Nul-voorwaardelijke operator kan ook worden gebruikt met void
functies. In dit geval wordt de instructie echter niet als null
geëvalueerd. Het voorkomt alleen een NullReferenceException
.
List<string> list = null;
list?.Add("hi"); // Does not evaluate to null
Gebruik met gebeurtenisoproep
Uitgaande van de volgende gebeurtenisdefinitie:
private event EventArgs OnCompleted;
Traditioneel is het verstandig om bij het oproepen van een evenement te controleren of het evenement null
als er geen abonnees aanwezig zijn:
var handler = OnCompleted;
if (handler != null)
{
handler(EventArgs.Empty);
}
Aangezien de nul-voorwaardelijke operator is geïntroduceerd, kan de aanroep worden teruggebracht tot één regel:
OnCompleted?.Invoke(EventArgs.Empty);
beperkingen
Null-voorwaardelijke operator produceert waarde, geen waarde, dat wil zeggen dat deze niet kan worden gebruikt voor eigendomstoewijzing, evenementabonnement, enz. De volgende code werkt bijvoorbeeld niet:
// 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
Let daar op:
int? nameLength = person?.Name.Length; // safe if 'person' is null
is niet hetzelfde als:
int? nameLength = (person?.Name).Length; // avoid this
omdat de eerste overeenkomt met:
int? nameLength = person != null ? (int?)person.Name.Length : null;
en dit laatste komt overeen met:
int? nameLength = (person != null ? person.Name : null).Length;
Ondanks de ternaire operator ?:
Wordt hier gebruikt om het verschil tussen twee gevallen uit te leggen, deze operators zijn niet gelijkwaardig. Dit kan eenvoudig worden aangetoond met het volgende voorbeeld:
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;
}
}
}
Welke uitgangen:
Null propagatie
Ik was gelezen
0
drietal
Ik was gelezen
Ik was gelezen
0
Om meerdere invocaties te voorkomen zou het volgende zijn:
var interimResult = foo.Bar;
Console.WriteLine(interimResult != null ? interimResult.Length : (int?)null);
En dit verschil verklaart enigszins waarom de nul-propagatie-operator nog niet wordt ondersteund in expressiebomen.
Gebruik statisch type
using static [Namespace.Type]
richtlijn using static [Namespace.Type]
kunt u statische leden van typen en opsommingswaarden importeren. Uitbreidingsmethoden worden geïmporteerd als uitbreidingsmethoden (van slechts één type), niet in het hoogste bereik.
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));
}
}
Verbeterde overbelastingsresolutie
Het volgende fragment toont een voorbeeld van het passeren van een methodegroep (in tegenstelling tot een lambda) wanneer een afgevaardigde wordt verwacht. Overbelastingresolutie lost dit nu op in plaats van een dubbelzinnige overbelastingsfout te veroorzaken vanwege het vermogen van C # 6 om het retourtype van de methode te controleren die is doorgegeven.
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;
}
}
resultaten:
Fout
CS0121-fout: de aanroep is dubbelzinnig tussen de volgende methoden of eigenschappen: 'Program.Overloaded (System.Action)' en 'Program.Overloaded (System.Func)'
C # 6 kan ook goed omgaan met het volgende geval van exacte matching voor lambda-expressies, wat zou hebben geresulteerd in een fout in C # 5 .
using System;
class Program
{
static void Foo(Func<Func<long>> func) {}
static void Foo(Func<Func<int>> func) {}
static void Main()
{
Foo(() => () => 7);
}
}
Kleine wijzigingen en bugfixes
Haakjes zijn nu verboden rond benoemde parameters. Het volgende compileert in C # 5, maar niet C # 6
Console.WriteLine((value: 23));
Operanden van is
en as
niet meer mogen werkwijze groepen. Het volgende compileert in C # 5, maar niet C # 6
var result = "".Any is byte;
De native compiler stond dit toe (hoewel het een waarschuwing liet zien) en controleerde zelfs de compatibiliteit van de extensiemethode niet, waardoor gekke dingen zoals
1.Any is string
ofIDisposable.Dispose is object
.
Zie deze referentie voor updates over wijzigingen.
Een uitbreidingsmethode gebruiken voor het initialiseren van collecties
De initialisatie-syntaxis van de verzameling kan worden gebruikt bij het instantiëren van elke klasse die IEnumerable
implementeert en een methode heeft met de naam Add
die een enkele parameter gebruikt.
In vorige versies moest deze methode Add
een instantiemethode zijn voor de klasse die wordt geïnitialiseerd. In C # 6 kan het ook een uitbreidingsmethode zijn.
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
}
}
Dit levert het volgende op:
Item toegevoegd met de methode voor het toevoegen van exemplaren: 1
Item toegevoegd met de methode voor het toevoegen van exemplaren: 2
Item toegevoegd met de methode voor het toevoegen van exemplaren: 3
Item toegevoegd met extensie add-methode: 4
Item toegevoegd met extensie add-methode: 5
Item toegevoegd met extensie add-methode: 6
Verbeteringen van waarschuwingen uitschakelen
In C # 5.0 en eerder kon de ontwikkelaar alleen waarschuwingen op nummer onderdrukken. Met de introductie van Roslyn Analyzers heeft C # een manier nodig om waarschuwingen van specifieke bibliotheken uit te schakelen. Met C # 6.0 kan de pragma-richtlijn waarschuwingen op naam onderdrukken.
Voordat:
#pragma warning disable 0501
C # 6.0:
#pragma warning disable CS0501