C# Language
C # 6.0 Funktioner
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:
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.
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ånwhen
klausulen,Exception
frånwhen
klausulen ignoreras och behandlas somfalse
. Detta tillvägagångssätt gör det möjligt för utvecklare att skrivawhen
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);
}
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;
}
Den vanliga metoden i tidigare versioner av C # var att logga och kasta undantaget.
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);
}
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
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:
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,
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
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;
}
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
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.
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.
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
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
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
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!
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
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ör på uppgifter 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
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
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
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 attclassroom
inte ärnull
. - Den andra
?
säkerställer att helaStudents
inte ärnull
. - Den tredje
?.
efter att indexeraren har säkerställt att[0]
indexeraren inte returnerade ettnull
. Det bör noteras att denna operation fortfarande kan kasta enIndexOutOfRangeException
.
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
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å.
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));
}
}
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:
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
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
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
ellerIDisposable.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