Sök…


Introduktion

Denna sjätte iteration av C # -språket tillhandahålls av Roslyn-kompilatorn. Denna kompilator kom ut med version 4.6 av .NET Framework, men den kan generera kod på ett bakåtkompatibelt sätt för att möjliggöra inriktning på tidigare ramversioner. C # version 6-kod kan kompileras på ett fullständigt bakåtkompatibelt sätt till .NET 4.0. Det kan också användas för tidigare ramverk, men vissa funktioner som kräver ytterligare ramstöd kanske inte fungerar korrekt.

Anmärkningar

Den sjätte versionen av C # släpptes juli 2015 tillsammans med Visual Studio 2015 och .NET 4.6.

Förutom att lägga till några nya språkfunktioner innehåller den en fullständig omskrivning av kompilatorn. Tidigare var csc.exe en inbyggd Win32-applikation skriven i C ++, med C # 6 är det nu en .NET-hanterad applikation skriven i C #. Denna omskrivning var känd som projektet "Roslyn" och koden är nu öppen källkod och tillgänglig på GitHub .

Operatörens namn

nameof operatören returnerar namnet på ett kodelement som en string . Detta är användbart när man kastar undantag relaterade till metodargument och även när man implementerar INotifyPropertyChanged .

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

nameof operatören utvärderas vid sammanställningstiden och ändrar uttrycket till en strängbokstav. Detta är också användbart för strängar som är uppkallad efter deras medlem som exponerar dem. Tänk på följande:

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

Eftersom nameof uttryck är kompilering tidskonstanter, kan de användas i attribut, case switch uttalanden, och så vidare.


Det är bekvämt att använda nameof med Enum s. Istället för:

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

det är möjligt att använda:

Console.WriteLine(nameof(Enum.One))

Utgången kommer att vara One i båda fallen.


nameof operatören kan komma åt icke-statiska medlemmar med hjälp av statisk-liknande syntax. Istället för att göra:

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

Kan ersättas med:

string lengthName = nameof(string.Length);

Utgången blir Length i båda exemplen. Det senare hindrar emellertid skapandet av onödiga instanser.


Även om nameof operatören fungerar med de flesta språkkonstruktioner, finns det vissa begränsningar. Till exempel kan du inte använda nameof operatören på öppna generiska typer eller metod returvärden:

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

Dessutom, om du tillämpar den på en generisk typ ignoreras parametern för generisk typ:

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

För fler exempel, se detta ämne som är dedikerat till nameof .


Lösning för tidigare versioner ( mer detaljer )

Även om nameof operatören inte existerar i C # för versioner före 6.0 kan liknande funktionalitet fås genom att använda MemberExpression enligt följande:

6,0

Uttryck:

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

Användande:

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

Observera att detta tillvägagångssätt gör att ett uttrycksträd skapas för varje samtal, så prestandan är mycket sämre jämfört med nameof operatören som utvärderas vid kompileringstid och har noll omkostnad vid körning.

Uttrycksfödda medlemmar

Medlemmar med uttrycksfödda funktioner tillåter användning av lambda-uttryck som medlemsorgan. För enkla medlemmar kan det resultera i renare och mer läsbar kod.

Expressionskroppsfunktioner kan användas för egenskaper, indexerare, metoder och operatörer.


Egenskaper

public decimal TotalPrice => BasePrice + Taxes;

Är ekvivalent med:

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

När en uttrycksfunktion används med en egenskap implementeras egenskapen som en getter-endast egenskap.

Visa demo


indexe

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

Är ekvivalent med:

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

metoder

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

Är ekvivalent med:

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

Som också kan användas med void metoder:

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

En åsidosättning av ToString kan läggas till i klassen Pair<T> :

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

Dessutom fungerar denna förenklade metod med det override sökordet:

public class Foo
{
    public int Bar { get; }

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

operatörer

Detta kan också användas av operatörer:

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

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

begränsningar

Medlemmar med uttrycksfria funktioner har vissa begränsningar. De kan inte innehålla blockuttalanden och andra uttalanden som innehåller block: if , switch , for , foreach , while , do , try , etc.

Vissa if uttalanden kan ersättas med ternära operatörer. Vissa uttalanden for och foreach kan konverteras till LINQ-frågor, till exempel:

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());

I alla andra fall kan den gamla syntaxen för funktionsmedlemmar användas.

Medlemmar med expressionsfunktioner kan innehålla async / await , men det är ofta överflödigt:

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

Kan ersättas med:

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

Undantagsfilter

Undantagsfilter ger utvecklarna möjlighet att lägga till ett villkor (i form av ett boolean uttryck) till ett fångstblock , vilket gör att catch kan utföras om villkoret utvärderas till true .

Undantagsfilter tillåter spridning av felsökningsinformation i det ursprungliga undantaget, när det att använda ett if uttalande i ett catch och kasta undantaget stoppar utbredningen av felsökningsinformation i det ursprungliga undantaget. Med undantagsfilter fortsätter undantaget att spridas uppåt i samtalstacken såvida inte villkoret är uppfyllt. Som ett resultat gör undantagsfilter felsökningsupplevelsen mycket enklare. I stället för att stanna på throw uttalande kommer debugger sluta ett uttalande kasta undantag, med det aktuella läget och alla lokala variabler bevaras. Kraschdumpar påverkas på liknande sätt.

Undantagsfilter har stöds av CLR sedan början och de har varit tillgängliga från VB.NET och F # i över ett decennium genom att avslöja en del av CLR: s undantagshanteringsmodell. Först efter lanseringen av C # 6.0 har funktionaliteten också varit tillgänglig för C # -utvecklare.


Använda undantagsfilter

Undantagsfilter används genom att lägga till en when klausul till catch . Det är möjligt att använda valfritt uttryck som returnerar en bool i en when klausul (utom väntar ). Den deklarerade undantagsvariabeln ex är tillgänglig från when klausulen:

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

Flera catch med when klausuler kan kombineras. Den första when klausulen återlämnar true kommer att få undantaget att fångas. Dess catch blocket kommer att föras, medan den andra catch klausuler kommer att ignoreras (deras when klausuler kommer inte att utvärderas). Till exempel:

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
{ ... }

Riskabelt när klausul

Varning

Det kan vara riskabelt att använda undantagsfilter: när ett Exception kastas från when klausulen, Exception från when klausulen ignoreras och behandlas som false . Detta tillvägagångssätt gör det möjligt för utvecklare att skriva when klausul utan att ta hand om ogiltiga fall.

Följande exempel illustrerar ett sådant 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);
}

Visa demo

Observera att undantagsfilter undviker de förvirrande linjenummerproblem som är förknippade med att använda throw när felkoden inte är inom samma funktion. Till exempel i detta fall rapporteras radnumret som 6 istället för 3:

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

Undantagsradnumret rapporteras som 6 på grund av att felet fångades och kastades på nytt med throw på rad 6.

Detsamma sker inte med undantagsfilter:

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

I detta exempel a är 0 då catch klausul ignoreras men 3 redovisas som rad nummer. Det beror på att de inte lossar bunten . Mer specifikt fångas undantaget inte på linje 5 eftersom a i själva verket är lika med 0 och det finns därför ingen möjlighet för undantaget att kastas om på linje 6 eftersom linje 6 inte körs.


Loggning som en biverkning

Metodsamtal i tillstånd kan orsaka biverkningar, så undantagsfilter kan användas för att köra kod på undantag utan att fånga dem. Ett vanligt exempel som utnyttjar detta är en Log metod som alltid returnerar false . Detta gör det möjligt att spåra logginformation under felsökning utan att behöva kasta undantaget igen.

Var medveten om att även om detta verkar vara ett bekvämt sätt att logga in, kan det vara riskabelt, speciellt om tredjeparts loggningsenheter används. Dessa kan kasta undantag när du loggar in i uppenbara situationer som kanske inte upptäcks lätt (se Riskabelt when(...) klausul ovan).

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

Visa demo

Den vanliga metoden i tidigare versioner av C # var att logga och kasta undantaget.

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

Visa demo


Det finally blocket

Det finally blocket körs varje gång om undantaget kastas eller inte. En subtilitet med uttryck i when är undantagsfilter körs längre upp i bunten innan de går in i de inre finally blocken. Detta kan orsaka oväntade resultat och beteenden när kod försöker ändra global status (som den aktuella trådens användare eller kultur) och sätta tillbaka den i ett finally block.

Exempel: finally blockera

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

Producerad produktion:

Start
Utvärderar: sant
Inre äntligen
Fånga
Yttre äntligen

Visa demo

I exemplet ovan, om metoden SomeOperation inte vill "läcka" det globala tillståndet ändras till den som ringer when klausuler, bör den också innehålla ett catch att modifiera tillståndet. Till exempel:

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

Det är också vanligt att se IDisposable utnyttja semantiken för att använda block för att uppnå samma mål, som IDisposable.Dispose kommer alltid att kallas innan ett undantag som kallas inom ett using börjar bubbla upp stacken.

Initierare av automatisk egendom

Introduktion

Egenskaper kan initialiseras med = operatören efter stängningen } . Klassen Coordinate nedan visar tillgängliga alternativ för att initialisera en egenskap:

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              
}

Tillbehör med olika synlighet

Du kan initialisera autoegenskaper som har olika synlighet på deras accessorer. Här är ett exempel med en skyddad setter:

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

Accessorn kan också vara internal , internal protected eller private .


Skrivskyddsegenskaper

Förutom flexibilitet med synlighet kan du också initiera läsbara autoegenskaper. Här är ett exempel:

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

Detta exempel visar också hur man initierar en egenskap med en komplex typ. Autoegenskaper kan inte vara skrivskyddade, så det hindrar även skrivinitialisering.


Gammal stil (före C # 6.0)

Innan C # 6 krävde detta mycket mer ordbok. Vi använde en extra variabel som kallas backing-egenskap för fastigheten för att ge standardvärde eller för att initiera den allmänna egendom som nedan,

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

Obs: Innan C # 6.0 kan du fortfarande initiera läsning och skrivning av autoimplementerade egenskaper (egenskaper med en getter och en setter) från konstruktören, men du kunde inte initialisera egenskapen i linje med dess deklaration

Visa demo


Användande

Initierare måste utvärdera till statiska uttryck, precis som fältinitierare. Om du behöver hänvisa till icke-statiska medlemmar kan du antingen initialisera egenskaper i konstruktörer som tidigare, eller använda uttryckskroppsegenskaper. Icke-statiska uttryck, som det nedan (kommenteras), genererar ett kompilatorfel:

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

decimal InitMe() { return 4m; }

Men statiska metoder kan användas för att initialisera autoegenskaper:

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

Den här metoden kan också tillämpas på egenskaper med olika åtkomstnivåer:

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

Initieraren med automatisk egendom tillåter tilldelning av egenskaper direkt inom deras deklaration. För skrivskyddskaraktärer tar det hand om alla krav som krävs för att säkerställa att fastigheten är obrukbar. Tänk till exempel klassen FingerPrint i följande exempel:

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

Visa demo


Varning:

Var noga med att inte förväxla autoegendom eller fältinitierare med liknande utseende uttryckskroppsmetoder som använder => motsats till = , och fält som inte inkluderar { get; } .

Till exempel är var och en av följande deklarationer olika.

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

Saknas { get; } i fastighetsdeklarationen resulterar i ett offentligt fält. Både skrivskyddad automatisk egendom Users1 och Users2 initialiseras endast en gång, men ett offentligt fält gör det möjligt att ändra samlingsinstans utanför klassen, vilket vanligtvis är oönskat. Att ändra en skrivskyddad autoegenskap med uttryckskropp till läsbar egendom med initialisering kräver inte bara att ta bort > från => , utan lägga till { get; } .

Den olika symbolen ( => istället för = ) i Users3 resulterar i att varje åtkomst till egenskapen returnerar en ny instans av HashSet<UserDto> som, medan giltig C # (från kompilatorens synvinkel) troligtvis inte är det önskade beteendet när används för en samling medlem.

Ovanstående kod motsvarar:

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

Indexinitierare

Indexinitierare gör det möjligt att skapa och initiera objekt med index på samma gång.

Detta gör att initialisering av ordböcker är mycket lätt:

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

Alla objekt som har en indexerad getter eller setter kan användas med denna syntax:

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

Produktion:

Index: foo, värde: 34
Index: stapel, värde: 42

Visa demo

Om klassen har flera indexerare är det möjligt att tilldela dem alla i en enda grupp uttalanden:

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

Produktion:

Index: foo, värde: 34
Index: stapel, värde: 42
Index: 10, värde: tio
Index: 42, värde: Livets mening

Det bör noteras att indexeraren set åtkomst kan bete sig annorlunda jämfört med en Add metod (som används i insamlings initializers).

Till exempel:

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

mot:

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

Stränginterpolering

Stränginterpolering gör det möjligt för utvecklaren att kombinera variables och text för att bilda en sträng.


Grundläggande exempel

Två int variabler skapas: foo och bar .

int foo = 34;
int bar = 42;

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

Console.WriteLine(resultString);

Utgång :

Foo är 34 och baren är 42.

Visa demo

Hängslen i strängar kan fortfarande användas, så här:

var foo = 34;
var bar = 42;

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

Detta ger följande utgång:

Foo är {foo}, och baren är {bar}.


Med hjälp av interpolering med ordliga strängbokstäver

Om du använder @ före strängen kommer strängen att tolkas ordentligt. Så t.ex. Unicode-tecken eller linjeavbrott förblir exakt som de har skrivits in. Detta kommer emellertid inte att påverka uttryck i en interpolerad sträng som visas i följande exempel:

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

Om det inte var klart:
\ u00B9
Foo
är 34,
och baren
är 42.

Visa demo


uttryck

Med stränginterpolering kan uttryck inom lockiga hängslen {} också utvärderas. Resultatet kommer att infogas på motsvarande plats i strängen. För att beräkna maximalt foo och bar och infoga det, använd Math.Max inom de lockiga hängslen:

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

Produktion:

Och den större är: 42

Obs: Alla ledande eller efterföljande blanksteg (inklusive mellanslag, flik och CRLF / ny linje) mellan det lockiga staget och uttrycket ignoreras helt och ingår inte i utgången

Visa demo

Som ett annat exempel kan variabler formateras som en valuta:

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

Produktion:

Foo formaterades som en valuta till fyra decimaler: $ 34,0000

Visa demo

Eller så kan de formateras som datum:

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

Produktion:

Idag är det: måndag 20 juli - 2015

Visa demo

Uttalanden med en villkorlig (ternär) operatör kan också utvärderas inom interpolationen. Dessa måste emellertid lindas i parentes, eftersom kolon annars används för att indikera formatering som visas ovan:

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

Produktion:

Baren är större än foo!

Visa demo

Villkorliga uttryck och formatspecifikationer kan blandas:

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

Produktion:

Miljö: 32-bitars process


Escape-sekvenser

Att undkomma backslash ( \ ) och citat ( " ) -tecken fungerar exakt samma i interpolerade strängar som i icke-interpolerade strängar, för både verbatim och icke-verbatim strängbokstäver:

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 \");

Produktion:

Foo är 34. I en icke-verbatim sträng måste vi fly "och \ med motstreck.
Foo är 34. I en verbatim sträng måste vi fly "med en extra offert, men vi behöver inte fly \

Om du vill inkludera en lockig stag { eller } i en interpolerad sträng använder du två lockiga hängslen {{ eller }} :

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

Produktion:

{foo} är: 34

Visa demo


FormattableString-typ

Typen av ett $"..." interpolationsuttryck $"..." är inte alltid en enkel sträng. Kompilatorn bestämmer vilken typ som ska tilldelas beroende på sammanhanget:

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

Detta är också ordningens typpreferens när kompilatorn behöver välja vilken överbelastad metod som kommer att kallas.

En ny typ , System.FormattableString , representerar en sammansatt formatsträng, tillsammans med argumenten som ska formateras. Använd det här för att skriva applikationer som hanterar interpolationsargumenten specifikt:

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();

    // ...
}

Ring ovanstående metod med:

AddLogItem($"The foo is {foo}, and the bar is {bar}.");
Till exempel kan man välja att inte ådra sig prestandakostnaderna för att formatera strängen om loggningsnivån redan skulle filtrera bort loggobjektet.

Implicita konverteringar

Det finns implicit typkonverteringar från en interpolerad sträng:

var s = $"Foo: {foo}";
System.IFormattable s = $"Foo: {foo}";
Du kan också producera en IFormattable variabel som låter dig konvertera strängen med invariant kontext:
var s = $"Bar: {bar}";
System.FormattableString s = $"Bar: {bar}";

Nuvarande och invariant kulturmetoder

Om kodanalys är aktiverad kommer alla interpolerade strängar att ge varning CA1305 (Specificera IFormatProvider ). En statisk metod kan användas för att tillämpa aktuell kultur.

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

För att producera en korrekt sträng för den aktuella kulturen, använd bara uttrycket:

Culture.Current($"interpolated {typeof(string).Name} string.")
Culture.Invariant($"interpolated {typeof(string).Name} string.")
Obs : Current och Invariant kan inte skapas som förlängningsmetoder eftersom kompilatorn som standard tilldelar typ String till interpolerat stränguttryck som får följande kod att inte kompilera:

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

FormattableString klassen innehåller metoden Invariant() , så det enklaste sättet att byta till invariant kultur är genom att förlita sig på att using static :

using static System.FormattableString;

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


Bakom kulisserna

Interpolerade strängar är bara ett syntaktiskt socker för String.Format() . Kompilatorn ( Roslyn ) förvandlar den till en String.Format bakom kulisserna:

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

Ovanstående konverteras till något liknande:

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

String Interpolation och Linq

Det är möjligt att använda interpolerade strängar i Linq-uttalanden för att öka läsbarheten ytterligare.

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

Kan skrivas om som:

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

Återanvändbara interpolerade strängar

Med string.Format kan du skapa återanvändbara formatsträngar:

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

// ...

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

Interpolerade strängar kommer dock inte att kompilera med platshållare som hänvisar till icke-existerande variabler. Följande kommer inte att sammanställas:

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

Skapa istället en Func<> som förbrukar variabler och returnerar en String :

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

// ...

Logger.Log(FormatError(ex));

Stränginterpolering och lokalisering

Om du lokaliserar din applikation kanske du undrar om det är möjligt att använda stränginterpolering tillsammans med lokalisering. Det skulle verkligen vara trevligt att ha möjlighet att lagra i resursfiler String som:

"My name is {name} {middlename} {surname}"
istället för det mycket mindre läsbara:

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

String sker vid kompileringstid , till skillnad från formateringssträng med sträng. string.Format som sker vid körning . Uttryck i en interpolerad sträng måste hänvisa till namn i det aktuella sammanhanget och måste lagras i resursfiler. Det betyder att om du vill använda lokalisering måste du göra det som:

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

Om resurssträngarna för de språk som används ovan lagras korrekt i de enskilda resursfilerna, bör du få följande utgång:

Bonjour, mon nom est John
Hallo, mein Namn ist John
Hej jag heter John

Observera att detta innebär att namnet följer den lokaliserade strängen på alla språk. Om så inte är fallet måste du lägga till platshållare i resurssträngarna och ändra funktionen ovan eller så måste du fråga om kulturinfo i funktionen och tillhandahålla en växelfallsdeklaration som innehåller de olika fallen. Mer information om resursfiler finns i Hur man använder lokalisering i C # .

Det är bra att använda ett standardspråk som de flesta förstår, om en översättning inte är tillgänglig. Jag föreslår att du använder engelska som standardspråk.

Rekursiv interpolering

Även om det inte är särskilt användbart är det tillåtet att använda en interpolerad string rekursivt inuti en annans lockiga parentes:

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

Produktion:

Strängen har 27 tecken:

Min klass heter MyClass.

Vänta på fångst och äntligen

Det är möjligt att använda await uttryck för att applicera vänta operatöruppgifter eller uppdrag (Of TResult) i catch och finally blockerar i C # 6.

Det var inte möjligt att använda await uttryck i catch och finally blockera i tidigare versioner på grund av kompilatorbegränsningar. C # 6 gör det mycket lättare att await på async-uppgifter genom att låta det await uttrycket.

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

Det krävdes i C # 5 att använda en bool eller förklara ett Exception utanför försökfångsten för att utföra asynkoperationer. Den här metoden visas i följande exempel:

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();

Noll förökning

?. operatör och operatör ?[...] kallas operatören med nollvillkor . Det hänvisas ibland även med andra namn, till exempel den säkra navigeringsoperatören .

Detta är användbart, för om . (medlem accessor) operatör appliceras på ett uttryck som utvärderar till null , programmet kommer att kasta en NullReferenceException . Om utvecklaren istället använder ?. (null-villkorad) operatör, kommer uttrycket att utvärderas till null istället för att kasta ett undantag.

Observera att om ?. operatör används och uttrycket är icke-noll, ?. och . är likvärdiga.


Grunderna

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

Visa demo

Om classroom inte har en lärare kan GetTeacher() returnera null . När den är null och egenskapen Name har åtkomst NullReferenceException en NullReferenceException .

Om vi ändrar detta uttalande för att använda ?. syntax, resultatet av hela uttrycket kommer att vara null :

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

Visa demo

Därefter, om classroom kunde vara null , kan vi också skriva detta uttalande som:

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

Visa demo

Detta är ett exempel på kortslutning: När någon villkorad åtkomstoperation som använder en nollvillkorad operatör utvärderar till noll utvärderas hela uttrycket till noll omedelbart utan att bearbeta resten av kedjan.

När terminalmedlemet i ett uttryck som innehåller den nollvillkorade operatören är av en värdetyp utvärderas uttrycket till ett Nullable<T> av den typen och kan därför inte användas som en direkt ersättning för uttrycket utan ?. .

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

Använd med Null-Coalescing Operator (??)

Du kan kombinera operatören med nollvillkor med Null-coalescing Operator ( ?? ) för att returnera ett standardvärde om uttrycket löser sig till null . Använd vårt exempel ovan:

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

Använd med indexerare

Operatören med nollvillkor kan användas med indexerare :

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

I exemplet ovan:

  • Den första ?. ser till att classroom inte är null .
  • Den andra ? säkerställer att hela Students inte är null .
  • Den tredje ?. efter att indexeraren har säkerställt att [0] indexeraren inte returnerade ett null . Det bör noteras att denna operation fortfarande kan kasta en IndexOutOfRangeException .

Använd med ogiltiga funktioner

Noll-villkorad operatör kan också användas med void funktioner. I det här fallet kommer dock uttalandet inte att utvärderas till null . Det kommer bara att förhindra en NullReferenceException .

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

Använd med händelseinbjudan

Antagande av följande händelsedefinition:

private event EventArgs OnCompleted;

När man åberopar en händelse är det traditionellt att pröva om händelsen är null om inga prenumeranter är närvarande:

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

Eftersom den nollvillkorade operatören har införts kan åkallandet reduceras till en enda rad:

OnCompleted?.Invoke(EventArgs.Empty);

begränsningar

Noll-villkorad operatör producerar rvalue, inte lvalue, det vill säga den kan inte användas för fastighetstilldelning, händelseabonnemang etc. Till exempel fungerar följande kod inte:

// 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

Anteckna det:

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

är inte samma sak som:

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

eftersom den förstnämnda motsvarar:

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

och det senare motsvarar:

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

Trots ternary operator ?: används här för att förklara skillnaden mellan två fall, dessa operatörer är inte likvärdiga. Detta kan enkelt demonstreras med följande exempel:

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

Vilka utgångar:

Noll förökning
Jag lästes
0
ternär
Jag lästes
Jag lästes
0

Visa demo

För att undvika flera invokationer skulle motsvarande vara:

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

Och denna skillnad förklarar något varför operatören för nollutbredning ännu inte stöds i uttrycksträd.

Med statisk typ

Det using static [Namespace.Type] -direktivet gör det möjligt att importera statiska medlemmar av typer och uppräkningsvärden. Tilläggsmetoder importeras som förlängningsmetoder (från bara en typ), inte i toppnivå.

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

Förbättrad överbelastningsupplösning

Följande fragment visar ett exempel på att passera en metodgrupp (i motsats till en lambda) när en delegat förväntas. Överbelastningsupplösning kommer nu att lösa detta istället för att höja ett tvetydigt överbelastningsfel på grund av förmågan hos C # 6 att kontrollera returtypen för metoden som har passerat.

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

Resultat:

6,0

Produktion

överbelastning med Func <int> uppringd

Visa demo

5,0

Fel

fel CS0121: Samtalet är tvetydigt mellan följande metoder eller egenskaper: 'Program.Overloaded (System.Action)' och 'Program.Overloaded (System.Func)'

C # 6 kan också hantera följande fall med exakt matchning för lambda-uttryck som skulle ha lett till ett fel i 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);
    }
}

Mindre förändringar och bugfixes

Parenteser är nu förbjudna kring namngivna parametrar. Följande sammanställs i C # 5, men inte C # 6

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

Operander av is och as inte längre tillåts vara metodgrupper. Följande sammanställs i C # 5, men inte C # 6

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

Den ursprungliga kompilatorn tillät detta (även om det visade en varning), och faktiskt inte ens kontrollera kompatibilitet med förlängningsmetoden, vilket tillåter galna saker som 1.Any is string eller IDisposable.Dispose is object .

Se denna referens för uppdateringar om ändringar.

Med hjälp av en förlängningsmetod för insamlingsinitialisering

Samlingsinitieringssyntax kan användas när man instanserar någon klass som implementerar IEnumerable och har en metod med namnet Add som tar en enda parameter.

I tidigare versioner måste denna Add metod vara en instansmetod i klassen som initieras. I C # 6 kan det också vara en förlängningsmetod.

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

Detta kommer att matas ut:

Objekt tillagd med instans tilläggsmetod: 1
Objekt tillagd med instans tilläggsmetod: 2
Objekt tillagd med instans tilläggsmetod: 3
Objekt tillagd med tilläggsmetod: 4
Objekt tillagd med tilläggsmetod: 5
Objekt läggs till med tilläggsmetod: 6

Inaktivera varningsförbättringar

I C # 5.0 och tidigare kunde utvecklaren bara undertrycka varningar efter nummer. Med introduktionen av Roslyn Analyzers behöver C # ett sätt att inaktivera varningar från specifika bibliotek. Med C # 6.0 kan pragma-direktivet undertrycka varningar med namn.

Innan:

#pragma warning disable 0501

C # 6.0:

#pragma warning disable CS0501


Modified text is an extract of the original Stack Overflow Documentation
Licensierat under CC BY-SA 3.0
Inte anslutet till Stack Overflow