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:

7,0
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:

7,0
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.

7,0
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_ eller 121.05_ )
  • bredvid decimalen ( 10_.0 )
  • bredvid exponenttecknet ( 1.1e_1 )
  • bredvid typspecifikationen ( 10_f )
  • omedelbart efter 0x eller 0b 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 Deconstructperson .

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

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

7,0
string s = o as string;
if(s != null)
{
    // do something with s
}

kan skrivas om som:

7,0
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

Roslyn Github-nummer

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.

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



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