C# Language
C # 7.0 Funktioner
Sök…
Introduktion
C # 7.0 är den sjunde versionen av C #. Denna version innehåller några nya funktioner: språkstöd för Tuples, lokala funktioner, out var
deklarationer, siffraseparatorer, binära bokstäver, mönster matchning, kasta uttryck, ref return
och ref local
och utvidgade uttryck bodied medlemmar lista.
Officiell referens: Vad är nytt i C # 7
ut var deklaration
Ett vanligt mönster i C # använder bool TryParse(object input, out object value)
att säkert analysera objekt.
out var
deklarationen är en enkel funktion för att förbättra läsbarheten. Det gör att en variabel kan deklareras samtidigt som den skickas som en ut-parameter.
En variabel som deklareras på detta sätt skopas till resten av kroppen vid den punkt där den deklareras.
Exempel
Med TryParse
före C # 7.0 måste du förklara en variabel för att få värdet innan du ringer funktionen:
int value;
if (int.TryParse(input, out value))
{
Foo(value); // ok
}
else
{
Foo(value); // value is zero
}
Foo(value); // ok
I C # 7.0 kan du ange deklarationen för variabeln som skickas till out
parametern och eliminera behovet av en separat variabeldeklaration:
if (int.TryParse(input, out var value))
{
Foo(value); // ok
}
else
{
Foo(value); // value is zero
}
Foo(value); // still ok, the value in scope within the remainder of the body
Om några parametrar som en funktion returnerar out
inte behövs kan du använda kasseringsoperatören _
.
p.GetCoordinates(out var x, out _); // I only care about x
En out var
deklaration kan användas med alla befintliga funktion som redan har out
parametrar. Syntaxen för funktionsdeklarationen förblir densamma, och inga ytterligare krav krävs för att göra funktionen kompatibel med en out var
deklaration. Denna funktion är helt enkelt syntaktiskt socker.
En annan funktion i out var
deklarationen är att den kan användas med anonyma typer.
var a = new[] { 1, 2, 3, 4, 5, 6, 7, 8, 9 };
var groupedByMod2 = a.Select(x => new
{
Source = x,
Mod2 = x % 2
})
.GroupBy(x => x.Mod2)
.ToDictionary(g => g.Key, g => g.ToArray());
if (groupedByMod2.TryGetValue(1, out var oddElements))
{
Console.WriteLine(oddElements.Length);
}
I den här koden skapar vi en Dictionary
med int
nyckel och matris av anonymt värde. I den tidigare versionen av C # var det omöjligt att använda metoden TryGetValue
här eftersom den krävde att du deklarerade out
variabeln (som är av anonym typ!). Men med out var
behöver vi inte uttryckligen ange typen av out
variabeln.
begränsningar
Observera att ut-deklarationer är av begränsad användning i LINQ-frågor eftersom uttryck tolkas som uttryck lambda-organ, så omfattningen av de införda variablerna är begränsad till dessa lambdas. Till exempel fungerar inte följande kod:
var nums =
from item in seq
let success = int.TryParse(item, out var tmp)
select success ? tmp : 0; // Error: The name 'tmp' does not exist in the current context
referenser
Binära bokstäver
Prefixet 0b kan användas för att representera binära bokstäver.
Binära bokstäver tillåter konstruktion av siffror från nollor och siffror, vilket gör det lättare att se vilka bitar som ställs in i den binära representationen av ett nummer. Detta kan vara användbart för att arbeta med binära flaggor.
Följande är likvärdiga sätt att specificera ett int
med värde 34
(= 2 5 + 2 1 ):
// Using a binary literal:
// bits: 76543210
int a1 = 0b00100010; // binary: explicitly specify bits
// Existing methods:
int a2 = 0x22; // hexadecimal: every digit corresponds to 4 bits
int a3 = 34; // decimal: hard to visualise which bits are set
int a4 = (1 << 5) | (1 << 1); // bitwise arithmetic: combining non-zero bits
Flaggor uppräkningar
enum
kunde specificera enum
för enum
endast göras med hjälp av en av de tre metoderna i detta exempel:
[Flags]
public enum DaysOfWeek
{
// Previously available methods:
// decimal hex bit shifting
Monday = 1, // = 0x01 = 1 << 0
Tuesday = 2, // = 0x02 = 1 << 1
Wednesday = 4, // = 0x04 = 1 << 2
Thursday = 8, // = 0x08 = 1 << 3
Friday = 16, // = 0x10 = 1 << 4
Saturday = 32, // = 0x20 = 1 << 5
Sunday = 64, // = 0x40 = 1 << 6
Weekdays = Monday | Tuesday | Wednesday | Thursday | Friday,
Weekends = Saturday | Sunday
}
Med binära bokstäver är det tydligare vilka bitar som ställs in, och att använda dem kräver inte förståelse av hexadecimala tal och bitvis aritmetik:
[Flags]
public enum DaysOfWeek
{
Monday = 0b00000001,
Tuesday = 0b00000010,
Wednesday = 0b00000100,
Thursday = 0b00001000,
Friday = 0b00010000,
Saturday = 0b00100000,
Sunday = 0b01000000,
Weekdays = Monday | Tuesday | Wednesday | Thursday | Friday,
Weekends = Saturday | Sunday
}
Sifferavskiljare
Understrekningen _
kan användas som en siffra-separator. Att kunna gruppera siffror i stora numeriska bokstäver har en betydande inverkan på läsbarheten.
Understrekningen kan förekomma var som helst i en numerisk bokstavskod, förutom som anges nedan. Olika grupper kan vara vettiga i olika scenarier eller med olika numeriska baser.
Vilken siffersekvens som helst kan separeras med en eller flera understreck. _
tillåtet i både decimaler och exponenter. Separatorerna har ingen semantisk inverkan - de ignoreras helt enkelt.
int bin = 0b1001_1010_0001_0100;
int hex = 0x1b_a0_44_fe;
int dec = 33_554_432;
int weird = 1_2__3___4____5_____6______7_______8________9;
double real = 1_000.111_1e-1_000;
Där _
siffraseparatorn inte får användas:
- i början av värdet (
_121
) - i slutet av värdet (
121_
eller121.05_
) - bredvid decimalen (
10_.0
) - bredvid exponenttecknet (
1.1e_1
) - bredvid typspecifikationen (
10_f
) - omedelbart efter
0x
eller0b
i binära och hexadecimala bokstäver ( kan ändras för att tillåta t.ex. 0b_1001_1000 )
Språkstöd för Tuples
Grunderna
En tupel är en ordnad, ändlig lista över element. Tuples används ofta i programmering som ett sätt att arbeta med en enda enhet kollektivt istället för att individuellt arbeta med var och en av tupelens element, och för att representera enskilda rader (dvs. "poster") i en relationsdatabas.
I C # 7.0 kan metoder ha flera avkastningsvärden. Bakom kulisserna kommer kompilatorn att använda den nya ValueTuple- strukturen.
public (int sum, int count) GetTallies()
{
return (1, 2);
}
Sidanmärkning : för att detta ska fungera i Visual Studio 2017 måste du få paketet System.ValueTuple
.
Om ett tuple-return metodresultat tilldelas en enda variabel kan du få åtkomst till medlemmarna med deras definierade namn i metodsignaturen:
var result = GetTallies();
// > result.sum
// 1
// > result.count
// 2
Tuple dekonstruktion
Tupeldekonstruktion separerar en tupel i dess delar.
Till exempel, åberopa GetTallies
och tilldela returvärdet till två separata variabler dekonstruerar tupeln till dessa två variabler:
(int tallyOne, int tallyTwo) = GetTallies();
var
fungerar också:
(var s, var c) = GetTallies();
Du kan också använda kortare syntax, med var
utanför ()
:
var (s, c) = GetTallies();
Du kan också dekonstruera till befintliga variabler:
int s, c;
(s, c) = GetTallies();
Att byta är nu mycket enklare (ingen temp-variabel behövs):
(b, a) = (a, b);
Intressant kan alla objekt dekonstrueras genom att definiera en Deconstruct
i klassen:
class Person
{
public string FirstName { get; set; }
public string LastName { get; set; }
public void Deconstruct(out string firstName, out string lastName)
{
firstName = FirstName;
lastName = LastName;
}
}
var person = new Person { FirstName = "John", LastName = "Smith" };
var (localFirstName, localLastName) = person;
I det här fallet (localFirstName, localLastName) = person
syntaxen åberopar Deconstruct
på person
.
Dekonstruktion kan till och med definieras i en förlängningsmetod. Detta motsvarar ovanstående:
public static class PersonExtensions
{
public static void Deconstruct(this Person person, out string firstName, out string lastName)
{
firstName = person.FirstName;
lastName = person.LastName;
}
}
var (localFirstName, localLastName) = person;
Ett alternativt tillvägagångssätt för Person
är att definiera själva Name
som en Tuple
. Tänk på följande:
class Person
{
public (string First, string Last) Name { get; }
public Person((string FirstName, string LastName) name)
{
Name = name;
}
}
Då kan du instansera en person som så (där vi kan ta en tupel som ett argument):
var person = new Person(("Jane", "Smith"));
var firstName = person.Name.First; // "Jane"
var lastName = person.Name.Last; // "Smith"
Tuple Initialisering
Du kan också skapa valpar i kod:
var name = ("John", "Smith");
Console.WriteLine(name.Item1);
// Outputs John
Console.WriteLine(name.Item2);
// Outputs Smith
När du skapar en tuple kan du tilldela ad hoc-objektnamn till medlemmarna i tupeln:
var name = (first: "John", middle: "Q", last: "Smith");
Console.WriteLine(name.first);
// Outputs John
Skriv inferens
Flera tupler definierade med samma signatur (matchande typer och räkning) kommer att sluts som matchningstyper. Till exempel:
public (int sum, double average) Measure(List<int> items)
{
var stats = (sum: 0, average: 0d);
stats.sum = items.Sum();
stats.average = items.Average();
return stats;
}
stats
kan returneras eftersom deklarationen av stats
och metodens retursignatur är en matchning.
Reflektions- och tupelfältnamn
Medlemsnamn finns inte vid körning. Reflektion kommer att betrakta tuples med samma antal och typer av medlemmar samma, även om medlemsnamnen inte matchar. Om du konverterar en tupel till ett object
och sedan till en tupel med samma medlemstyper, men olika namn, kommer det inte att orsaka något undantag.
Medan klassen ValueTuple inte bevarar information för medlemsnamn, är informationen tillgänglig genom reflektion i en TupleElementNamesAttribute. Detta attribut tillämpas inte på själva tupeln utan på metodparametrar, returvärden, egenskaper och fält. Detta gör att namnen på tuple-objekt kan bevaras över enheter, dvs. om en metod returnerar (strängnamn, int-räkning) kommer namnen och räkningen att finnas tillgängliga för anropare av metoden i en annan enhet eftersom returvärdet kommer att markeras med TupleElementNameAttribute som innehåller värdena "namn" och "räkna".
Används med generika och async
De nya tupelfunktionerna (med hjälp av den underliggande ValueTuple
typen) stöder helt generiska och kan användas som generisk typparameter. Det gör det möjligt att använda dem med async
/ await
mönster:
public async Task<(string value, int count)> GetValueAsync()
{
string fooBar = await _stackoverflow.GetStringAsync();
int num = await _stackoverflow.GetIntAsync();
return (fooBar, num);
}
Använd med samlingar
Det kan bli fördelaktigt att ha en samling tuples i (som exempel) ett scenario där du försöker hitta en matchande tupel baserad på förhållanden för att undvika kodförgrening.
Exempel:
private readonly List<Tuple<string, string, string>> labels = new List<Tuple<string, string, string>>()
{
new Tuple<string, string, string>("test1", "test2", "Value"),
new Tuple<string, string, string>("test1", "test1", "Value2"),
new Tuple<string, string, string>("test2", "test2", "Value3"),
};
public string FindMatchingValue(string firstElement, string secondElement)
{
var result = labels
.Where(w => w.Item1 == firstElement && w.Item2 == secondElement)
.FirstOrDefault();
if (result == null)
throw new ArgumentException("combo not found");
return result.Item3;
}
Med de nya tuplesna kan bli:
private readonly List<(string firstThingy, string secondThingyLabel, string foundValue)> labels = new List<(string firstThingy, string secondThingyLabel, string foundValue)>()
{
("test1", "test2", "Value"),
("test1", "test1", "Value2"),
("test2", "test2", "Value3"),
}
public string FindMatchingValue(string firstElement, string secondElement)
{
var result = labels
.Where(w => w.firstThingy == firstElement && w.secondThingyLabel == secondElement)
.FirstOrDefault();
if (result == null)
throw new ArgumentException("combo not found");
return result.foundValue;
}
Även om namnet på exemplet tupeln ovan är ganska generiskt, möjliggör idén med relevanta etiketter en djupare förståelse av vad som försöks i koden för att hänvisa till "item1", "item2" och "item3".
Skillnader mellan ValueTuple och Tuple
Det främsta skälet till introduktion av ValueTuple
är prestanda.
Skriv namn | ValueTuple | Tuple |
---|---|---|
Klass eller struktur | struct | class |
Mutabilitet (ändra värden efter skapandet) | föränderlig | oföränderlig |
Namnge medlemmar och annat språkstöd | ja | nej ( TBD ) |
referenser
- Original Tuples språkfunktion på GitHub
- En körbar VS 15-lösning för C # 7.0-funktioner
- NuGet Tuple-paket
Lokala funktioner
Lokala funktioner definieras i en metod och är inte tillgängliga utanför den. De har tillgång till alla lokala variabler och stöder iteratorer, async
/ await
och lambda-syntax. På detta sätt kan repetitioner som är specifika för en funktion funktionaliseras utan att trånga klassen. Som en biverkning förbättrar detta prestandan för intelligensförslag.
Exempel
double GetCylinderVolume(double radius, double height)
{
return getVolume();
double getVolume()
{
// You can declare inner-local functions in a local function
double GetCircleArea(double r) => Math.PI * r * r;
// ALL parents' variables are accessible even though parent doesn't have any input.
return GetCircleArea(radius) * height;
}
}
Lokala funktioner förenklar avsevärt koden för LINQ-operatörer, där du vanligtvis måste separera argumentkontroller från den faktiska logiken för att göra argumentkontroller omedelbart, inte försenade förrän efter att iterationen startade.
Exempel
public static IEnumerable<TSource> Where<TSource>(
this IEnumerable<TSource> source,
Func<TSource, bool> predicate)
{
if (source == null) throw new ArgumentNullException(nameof(source));
if (predicate == null) throw new ArgumentNullException(nameof(predicate));
return iterator();
IEnumerable<TSource> iterator()
{
foreach (TSource element in source)
if (predicate(element))
yield return element;
}
}
Lokala funktioner stöder också async
och await
nyckelord.
Exempel
async Task WriteEmailsAsync()
{
var emailRegex = new Regex(@"(?i)[a-z0-9_.+-]+@[a-z0-9-]+\.[a-z0-9-.]+");
IEnumerable<string> emails1 = await getEmailsFromFileAsync("input1.txt");
IEnumerable<string> emails2 = await getEmailsFromFileAsync("input2.txt");
await writeLinesToFileAsync(emails1.Concat(emails2), "output.txt");
async Task<IEnumerable<string>> getEmailsFromFileAsync(string fileName)
{
string text;
using (StreamReader reader = File.OpenText(fileName))
{
text = await reader.ReadToEndAsync();
}
return from Match emailMatch in emailRegex.Matches(text) select emailMatch.Value;
}
async Task writeLinesToFileAsync(IEnumerable<string> lines, string fileName)
{
using (StreamWriter writer = File.CreateText(fileName))
{
foreach (string line in lines)
{
await writer.WriteLineAsync(line);
}
}
}
}
En viktig sak som ni kanske har märkt är att kan definieras lokala funktioner under return
uttalande, behöver de inte skall definieras ovanför. Dessutom följer lokala funktioner vanligtvis "lowCamelCase" namnkonventionen för att lättare kunna skilja sig från klassomfångsfunktioner.
Mönstermatchning
Mönstermatchningstillägg för C # möjliggör många av fördelarna med mönstermatchning från funktionella språk, men på ett sätt som smidigt integreras med känslan av det underliggande språket
switch
uttryck
Mönstermatchning utvidgar switch
till att slå på typer:
class Geometry {}
class Triangle : Geometry
{
public int Width { get; set; }
public int Height { get; set; }
public int Base { get; set; }
}
class Rectangle : Geometry
{
public int Width { get; set; }
public int Height { get; set; }
}
class Square : Geometry
{
public int Width { get; set; }
}
public static void PatternMatching()
{
Geometry g = new Square { Width = 5 };
switch (g)
{
case Triangle t:
Console.WriteLine($"{t.Width} {t.Height} {t.Base}");
break;
case Rectangle sq when sq.Width == sq.Height:
Console.WriteLine($"Square rectangle: {sq.Width} {sq.Height}");
break;
case Rectangle r:
Console.WriteLine($"{r.Width} {r.Height}");
break;
case Square s:
Console.WriteLine($"{s.Width}");
break;
default:
Console.WriteLine("<other>");
break;
}
}
is
uttryck
Mönstermatchning utökar is
operatör för att kontrollera om en typ och deklarera en ny variabel samtidigt.
Exempel
string s = o as string;
if(s != null)
{
// do something with s
}
kan skrivas om som:
if(o is string s)
{
//Do something with s
};
Noterar också att omfattningen av mönstret variabeln s
utsträcks till utanför if
blocket nått slutet av det omslutande omfattning, exempel:
if(someCondition)
{
if(o is string s)
{
//Do something with s
}
else
{
// s is unassigned here, but accessible
}
// s is unassigned here, but accessible
}
// s is not accessible here
ref return och ref local
Ref-returer och ref-lokalbefolkningen är användbara för att manipulera och returnera referenser till minnesblock istället för att kopiera minne utan att använda osäkra pekare.
Ref Return
public static ref TValue Choose<TValue>(
Func<bool> condition, ref TValue left, ref TValue right)
{
return condition() ? ref left : ref right;
}
Med detta kan du skicka två värden med referens, varvid en av dem returneras baserat på vissa villkor:
Matrix3D left = …, right = …;
Choose(chooser, ref left, ref right).M20 = 1.0;
Ref Local
public static ref int Max(ref int first, ref int second, ref int third)
{
ref int max = first > second ? ref first : ref second;
return max > third ? ref max : ref third;
}
…
int a = 1, b = 2, c = 3;
Max(ref a, ref b, ref c) = 4;
Debug.Assert(a == 1); // true
Debug.Assert(b == 2); // true
Debug.Assert(c == 4); // true
Osäker ref-operation
I System.Runtime.CompilerServices.Unsafe
en uppsättning osäkra operationer definierats som låter dig manipulera ref
värden som om de var pekare, i princip.
Tolkar till exempel en minnesadress ( ref
) som en annan typ:
byte[] b = new byte[4] { 0x42, 0x42, 0x42, 0x42 };
ref int r = ref Unsafe.As<byte, int>(ref b[0]);
Assert.Equal(0x42424242, r);
0x0EF00EF0;
Assert.Equal(0xFE, b[0] | b[1] | b[2] | b[3]);
Var dock försiktig med ändan när du gör detta, t.ex., kontrollera BitConverter.IsLittleEndian
om det behövs och hantera därefter.
Eller iterera över en matris på ett osäkert sätt:
int[] a = new int[] { 0x123, 0x234, 0x345, 0x456 };
ref int r1 = ref Unsafe.Add(ref a[0], 1);
Assert.Equal(0x234, r1);
ref int r2 = ref Unsafe.Add(ref r1, 2);
Assert.Equal(0x456, r2);
ref int r3 = ref Unsafe.Add(ref r2, -3);
Assert.Equal(0x123, r3);
Eller liknande Subtract
:
string[] a = new string[] { "abc", "def", "ghi", "jkl" };
ref string r1 = ref Unsafe.Subtract(ref a[0], -2);
Assert.Equal("ghi", r1);
ref string r2 = ref Unsafe.Subtract(ref r1, -1);
Assert.Equal("jkl", r2);
ref string r3 = ref Unsafe.Subtract(ref r2, 3);
Assert.Equal("abc", r3);
Dessutom kan man kontrollera om två ref
värden är samma, dvs. samma adress:
long[] a = new long[2];
Assert.True(Unsafe.AreSame(ref a[0], ref a[0]));
Assert.False(Unsafe.AreSame(ref a[0], ref a[1]));
länkar
System.Runtime.CompilerServices.Ansafe på github
kasta uttryck
C # 7.0 tillåter kast som ett uttryck på vissa platser:
class Person
{
public string Name { get; }
public Person(string name) => Name = name ?? throw new ArgumentNullException(nameof(name));
public string GetFirstName()
{
var parts = Name.Split(' ');
return (parts.Length > 0) ? parts[0] : throw new InvalidOperationException("No name!");
}
public string GetLastName() => throw new NotImplementedException();
}
Innan C # 7.0 skulle du behöva: om du ville kasta ett undantag från en uttryckskropp:
var spoons = "dinner,desert,soup".Split(',');
var spoonsArray = spoons.Length > 0 ? spoons : null;
if (spoonsArray == null)
{
throw new Exception("There are no spoons");
}
Eller
var spoonsArray = spoons.Length > 0
? spoons
: new Func<string[]>(() =>
{
throw new Exception("There are no spoons");
})();
I C # 7.0 är ovanstående nu förenklad till:
var spoonsArray = spoons.Length > 0 ? spoons : throw new Exception("There are no spoons");
Lista över utvidgade uttryck med medlemmar
C # 7.0 lägger tillbehör, konstruktörer och slutförare till listan över saker som kan ha uttryckskroppar:
class Person
{
private static ConcurrentDictionary<int, string> names = new ConcurrentDictionary<int, string>();
private int id = GetId();
public Person(string name) => names.TryAdd(id, name); // constructors
~Person() => names.TryRemove(id, out _); // finalizers
public string Name
{
get => names[id]; // getters
set => names[id] = value; // setters
}
}
Se även avsnittet om var-deklarationen för kasseringsoperatören.
ValueTask
Task<T>
är en klass och orsakar onödiga omkostnader för dess tilldelning när resultatet är omedelbart tillgängligt.
ValueTask<T>
är en struktur och har införts för att förhindra allokering av ett Task
i händelse av att resultatet av asynkoperationen redan är tillgängligt vid tidpunkten för att vänta.
Så ValueTask<T>
ger två fördelar:
1. Prestationsökning
Här är ett Task<T>
exempel:
- Kräver tilldelning av hög
- Tar 120ns med JIT
async Task<int> TestTask(int d)
{
await Task.Delay(d);
return 10;
}
Här är det analoga ValueTask<T>
exemplet:
- Ingen hoptilldelning om resultatet är känt synkront (vilket det inte är i det här fallet på grund av
Task.Delay
, men ofta finns i många verkligaasync
/await
verkligheten) - Tar 65ns med JIT
async ValueTask<int> TestValueTask(int d)
{
await Task.Delay(d);
return 10;
}
2. Ökad flexibilitet i genomförandet
Implementeringar av ett asynkgränssnitt som önskar vara synkront skulle annars tvingas använda antingen Task.Run
eller Task.FromResult
(vilket resulterar i prestationsstraffet som diskuterats ovan). Därför finns det ett visst tryck mot synkrona implementationer.
Men med ValueTask<T>
är implementeringar mer fria att välja mellan att vara synkrona eller asynkrona utan att påverka anropare.
Här är till exempel ett gränssnitt med en asynkron metod:
interface IFoo<T>
{
ValueTask<T> BarAsync();
}
... och här är hur den metoden kan kallas:
IFoo<T> thing = getThing();
var x = await thing.BarAsync();
Med ValueTask
koden ovan med antingen synkrona eller asynkrona implementationer :
Synkron implementering:
class SynchronousFoo<T> : IFoo<T>
{
public ValueTask<T> BarAsync()
{
var value = default(T);
return new ValueTask<T>(value);
}
}
Asynkron implementering
class AsynchronousFoo<T> : IFoo<T>
{
public async ValueTask<T> BarAsync()
{
var value = default(T);
await Task.Delay(1);
return value;
}
}
anteckningar
Även om ValueTask
strukturen planerades att läggas till C # 7.0 , har den förvarats som ett annat bibliotek för tillfället. ValueTask <T> System.Threading.Tasks.Extensions
kan laddas ner från Nuget Gallery