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:

6.0

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.

Demo bekijken


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 de when clausule, wordt de Exception van de when clausule genegeerd en als false behandeld. Met deze aanpak kunnen ontwikkelaars schrijven when 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);
}

Demo bekijken

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

Demo bekijken

De gebruikelijke aanpak in eerdere versies van C # was het loggen en opnieuw gooien van de uitzondering.

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 bekijken


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

Demo bekijken

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:

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              
}

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,

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

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

Demo bekijken


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

Demo bekijken


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

Demo bekijken

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.

Demo bekijken

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.

Demo bekijken


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

Demo bekijken

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

Demo bekijken

Of ze kunnen worden opgemaakt als datums:

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

Output:

Vandaag is: maandag 20 juli 2015

Demo bekijken

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!

Demo bekijken

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

Demo bekijken


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

Demo bekijken

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

Demo bekijken

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

Demo bekijken

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 het classroom niet null .
  • De tweede ? zorgt ervoor dat de hele verzameling Students niet null .
  • De derde ?. nadat de indexer ervoor zorgt dat de [0] indexer geen null object heeft geretourneerd. Opgemerkt moet worden dat deze bewerking nog steeds een IndexOutOfRangeException kan IndexOutOfRangeException .

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

Demo bekijken

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.

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 Fiddle

6.0
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:

6.0

uitgang

overbelasting met Func <int> genoemd

Demo bekijken

5.0

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

5.0
Console.WriteLine((value: 23));

Operanden van is en as niet meer mogen werkwijze groepen. Het volgende compileert in C # 5, maar niet C # 6

5.0
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 of IDisposable.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


Modified text is an extract of the original Stack Overflow Documentation
Licentie onder CC BY-SA 3.0
Niet aangesloten bij Stack Overflow