Suche…


Einführung

C # 7.0 ist die siebte Version von C #. Diese Version enthält einige neue Funktionen: Sprachunterstützung für Tupel, lokale Funktionen, out var Deklarationen, Zifferntrennzeichen, Binärliterale, Musterabgleich, Wurfausdrücke, ref return und ref local Liste der ref local und erweiterten Ausdruckselemente.

Offizielle Referenz: Was ist neu in C # 7?

aus var deklaration

Ein übliches Muster in C # ist die Verwendung von bool TryParse(object input, out object value) , um Objekte sicher zu analysieren.

Die out var Deklaration ist eine einfache Funktion zur Verbesserung der Lesbarkeit. Es ermöglicht, dass eine Variable gleichzeitig als deklarierter Parameter deklariert wird.

Eine auf diese Weise deklarierte Variable bezieht sich auf den Rest des Körpers an der Stelle, an der sie deklariert wird.

Beispiel

Bei Verwendung von TryParse vor C # 7.0 müssen Sie vor dem Aufruf der Funktion eine Variable deklarieren, die den Wert erhalten soll:

7,0
int value;
if (int.TryParse(input, out value)) 
{
    Foo(value); // ok
}
else
{
    Foo(value); // value is zero
}

Foo(value); // ok

In C # 7.0 können Sie die Deklaration der an den out Parameter übergebenen Variablen einschließen, sodass keine separate Variablendeklaration erforderlich ist:

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

Wenn einige der Parameter, die eine Funktion in out zurückgibt out nicht benötigt werden, können Sie den Verwerfungsoperator _ .

p.GetCoordinates(out var x, out _); // I only care about x

Eine out var Deklaration kann mit jeder vorhandenen Funktion verwendet werden, für die bereits out Parameter vorhanden sind. Die Funktionsdeklarationssyntax bleibt gleich und es sind keine zusätzlichen Anforderungen erforderlich, um die Funktion mit einer out var Deklaration kompatibel zu machen. Diese Funktion ist einfach syntaktischer Zucker.

Eine andere Funktion der out var Deklaration ist, dass sie mit anonymen Typen verwendet werden kann.

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

In diesem Code erstellen wir ein Dictionary mit einem int Schlüssel und einem Array mit einem anonymen Typwert. In der vorherigen Version von C # war es nicht möglich, die TryGetValue Methode hier zu verwenden, da Sie die out Variable (die vom anonymen Typ ist!) TryGetValue . Mit out var wir jedoch den Typ der out Variablen nicht explizit angeben.

Einschränkungen

Beachten Sie, dass out-Var-Deklarationen in LINQ-Abfragen nur von begrenztem Nutzen sind, da Ausdrücke als Ausdrucks-Lambda-Körper interpretiert werden. Der Umfang der eingeführten Variablen ist daher auf diese Lambdas beschränkt. Der folgende Code funktioniert beispielsweise nicht:

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

Verweise

Binäre Literale

Das 0b- Präfix kann verwendet werden, um binäre Literale darzustellen.

Binäre Literale ermöglichen das Erstellen von Zahlen aus Nullen und Einsen. Dadurch wird deutlich, welche Bits in der binären Darstellung einer Zahl gesetzt sind. Dies kann nützlich sein, wenn Sie mit binären Flags arbeiten.

Im Folgenden sind gleichwertige Möglichkeiten zum Angeben eines int mit dem Wert 34 (= 2 5 + 2 1 ) angegeben:

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

Flags Aufzählungen

Das Festlegen von Flagwerten für eine enum war zuvor nur mit einer der drei Methoden in diesem Beispiel möglich:

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

Bei binären Literalen ist es offensichtlicher, welche Bits gesetzt sind, und deren Verwendung erfordert kein Verständnis der Hexadezimalzahlen und der bitweisen Arithmetik:

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

Zifferntrennzeichen

Der Unterstrich _ kann als Zifferntrennzeichen verwendet werden. Die Möglichkeit, Ziffern in großen numerischen Literalen zu gruppieren, hat einen erheblichen Einfluss auf die Lesbarkeit.

Der Unterstrich kann an einer beliebigen Stelle in einem numerischen Literal vorkommen, außer wie unten angegeben. Unterschiedliche Gruppierungen können in verschiedenen Szenarien oder mit unterschiedlichen numerischen Grundlagen sinnvoll sein.

Jede Ziffernfolge kann durch einen oder mehrere Unterstriche getrennt werden. Das _ ist sowohl in Dezimalzahlen als auch in Exponenten zulässig. Die Trennzeichen haben keine semantischen Auswirkungen - sie werden einfach ignoriert.

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;

Wenn das _ Trennzeichen nicht verwendet werden darf:

Sprachunterstützung für Tupel

Grundlagen

Ein Tupel ist eine geordnete, endliche Liste von Elementen. Tupel werden üblicherweise in der Programmierung als Mittel verwendet, um mit einer einzelnen Entität zusammenzuarbeiten, anstatt einzeln mit jedem der Elemente des Tupels zu arbeiten, und um einzelne Zeilen (dh "Datensätze") in einer relationalen Datenbank darzustellen.

In C # 7.0 können Methoden mehrere Rückgabewerte enthalten. Hinter den Kulissen verwendet der Compiler die neue ValueTuple- Struktur.

public (int sum, int count) GetTallies() 
{
    return (1, 2);
}

Randbemerkung: für das in Visual Studio 2017 zu arbeiten, müssen Sie die bekommen System.ValueTuple Paket.

Wenn ein Ergebnis für eine Methode, die ein Tupel zurückgibt, einer einzelnen Variablen zugewiesen wird, können Sie auf die Member über ihre definierten Namen in der Methodensignatur zugreifen:

var result = GetTallies();
// > result.sum
// 1
// > result.count
// 2

Tuple Dekonstruktion

Tupel Dekonstruktion trennt ein Tupel in seine Teile.

Wenn Sie beispielsweise GetTallies und den Rückgabewert zwei separaten Variablen GetTallies , dekonstruieren Sie das Tupel in diese beiden Variablen:

(int tallyOne, int tallyTwo) = GetTallies();

var funktioniert auch:

(var s, var c) = GetTallies();

Sie können auch eine kürzere Syntax verwenden, wobei var außerhalb von () :

var (s, c) = GetTallies();

Sie können auch in vorhandene Variablen zerlegen:

int s, c;
(s, c) = GetTallies();

Das Tauschen ist jetzt viel einfacher (keine temporäre Variable erforderlich):

(b, a) = (a, b);

Interessanterweise kann jedes Objekt dekonstruiert werden, indem eine Deconstruct Methode in der Klasse definiert wird:

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;

In diesem Fall ruft die person (localFirstName, localLastName) = person Deconstruct für die person .

Dekonstruktion kann sogar in einer Erweiterungsmethode definiert werden. Dies entspricht dem oben genannten:

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;

Ein alternativer Ansatz für die Person Klasse besteht darin, den Name selbst als Tuple zu definieren. Folgendes berücksichtigen:

class Person
{
    public (string First, string Last) Name { get; }

    public Person((string FirstName, string LastName) name)
    {
        Name = name;
    }
}

Dann können Sie eine Person so instanziieren (wo wir ein Tupel als Argument nehmen können):

var person = new Person(("Jane", "Smith"));

var firstName = person.Name.First; // "Jane"
var lastName = person.Name.Last;   // "Smith"

Tupel-Initialisierung

Sie können Tupel auch beliebig im Code erstellen:

var name = ("John", "Smith");
Console.WriteLine(name.Item1);
// Outputs John

Console.WriteLine(name.Item2);
// Outputs Smith

Beim Erstellen eines Tupels können Sie den Mitgliedern des Tupels Ad-hoc-Objektnamen zuweisen:

var name = (first: "John", middle: "Q", last: "Smith");
Console.WriteLine(name.first);
// Outputs John

Inferenz eingeben

Mehrere Tupel, die mit derselben Signatur (übereinstimmende Typen und Anzahl) definiert wurden, werden als übereinstimmende Typen abgeleitet. Zum Beispiel:

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 können zurückgegeben werden, da die Deklaration der Variablen " stats und die Rückgabesignatur der Methode übereinstimmen.

Reflexions- und Tupelfeldnamen

Mitgliedsnamen sind zur Laufzeit nicht vorhanden. In Reflection werden Tupel mit derselben Anzahl und Elementtypen als identisch betrachtet, auch wenn die Mitgliedsnamen nicht übereinstimmen. Das Konvertieren eines Tupels in ein object und dann in ein Tupel mit denselben Mitgliedstypen, aber unterschiedlichen Namen, führt ebenfalls nicht zu einer Ausnahme.

Während die ValueTuple-Klasse selbst keine Informationen für Mitgliedsnamen speichert, sind die Informationen durch Reflektion in einem TupleElementNamesAttribute verfügbar. Dieses Attribut wird nicht auf das Tupel selbst angewendet, sondern auf Methodenparameter, Rückgabewerte, Eigenschaften und Felder. Dadurch können die Namen der Tupel-Elemente in allen Assemblys beibehalten werden. Wenn also eine Methode (String-Name, Int-Anzahl) zurückgibt, stehen Name und Anzahl der Namen den Aufrufern der Methode in einer anderen Assembly zur Verfügung, da der Rückgabewert mit TupleElementNameAttribute mit den Werten markiert wird "name" und "count".

Verwendung mit Generika und async

Die neuen Tuple-Features (mit dem zugrunde liegenden ValueTuple Typ) unterstützen Generics vollständig und können als generische Typparameter verwendet werden. Dadurch ist es möglich, sie mit dem async / await Muster zu verwenden:

public async Task<(string value, int count)> GetValueAsync()
{
    string fooBar = await _stackoverflow.GetStringAsync();
    int num = await _stackoverflow.GetIntAsync();

    return (fooBar, num);
}

Verwenden Sie mit Sammlungen

Es kann von Vorteil sein, eine Sammlung von Tupeln in einem Szenario (als Beispiel) zu haben, in dem Sie versuchen, ein übereinstimmendes Tupel basierend auf Bedingungen zu finden, um eine Verzweigung des Codes zu vermeiden.

Beispiel:

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

Mit den neuen Tupeln können:

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

Obwohl die Benennung im obigen Beispieltupel ziemlich allgemein ist, ermöglicht die Idee der relevanten Labels ein tieferes Verständnis dessen, was im Code versucht wird, wenn auf "item1", "item2" und "item3" verwiesen wird.

Unterschiede zwischen ValueTuple und Tuple

Der Hauptgrund für die Einführung von ValueTuple ist die Leistung.

Modellname ValueTuple Tuple
Klasse oder Struktur struct class
Mutabilität (Werte nach Erstellung ändern) veränderlich unveränderlich
Benennen von Mitgliedern und Unterstützung anderer Sprachen Ja nein ( TBD )

Verweise

Lokale Funktionen

Lokale Funktionen werden innerhalb einer Methode definiert und stehen außerhalb nicht zur Verfügung. Sie haben Zugriff auf alle lokalen Variablen und unterstützen Iteratoren, async / await und Lambda-Syntax. Auf diese Weise können für eine Funktion spezifische Wiederholungen funktionalisiert werden, ohne die Klasse zu überfordern. Als Nebeneffekt verbessert dies die Intellisense-Vorschlagsleistung.

Beispiel

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

Lokale Funktionen vereinfachen den Code für LINQ-Operatoren erheblich. In diesem Fall müssen Sie normalerweise Argumentprüfungen von der tatsächlichen Logik trennen, um Argumentprüfungen sofort durchzuführen, und nicht verzögert, bis die Iteration gestartet wird.

Beispiel

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

Lokale Funktionen unterstützen auch die async und await Schlüsselwörter.

Beispiel

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

Eine wichtige Sache, die Sie möglicherweise bemerkt haben, ist, dass lokale Funktionen unter der return Anweisung definiert werden können. Sie müssen nicht darüber definiert werden. Außerdem folgen lokale Funktionen normalerweise der Namenskonvention "lowerCamelCase", um sich leichter von Klassenbereichsfunktionen zu unterscheiden.

Musterabgleich

Pattern-Matching-Erweiterungen für C # ermöglichen viele Vorteile des Pattern-Matchings aus funktionalen Sprachen, jedoch auf eine Weise, die sich nahtlos in das Gefühl der zugrunde liegenden Sprache einfügt

Ausdruck switch

Pattern Matching erweitert die switch Anweisung um Typen zu aktivieren:

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 Ausdruck

Der Musterabgleich erweitert den is Operator, um nach einem Typ zu suchen und gleichzeitig eine neue Variable zu deklarieren.

Beispiel

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

kann umgeschrieben werden als:

7,0
if(o is string s)
{
    //Do something with s
};

Beachten Sie auch, dass der Gültigkeitsbereich der Mustervariablen s außerhalb des if Blocks liegt und das Ende des umschließenden Gültigkeitsbereichs erreicht. Beispiel:

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 und ref local

Ref Returns und Ref-Locals sind nützlich, um Verweise auf Speicherblöcke zu bearbeiten und zurückzugeben, anstatt Speicher zu kopieren, ohne auf unsichere Zeiger zurückzugreifen.

Ref Return

public static ref TValue Choose<TValue>(
    Func<bool> condition, ref TValue left, ref TValue right)
{
    return condition() ? ref left : ref right;
}

Damit können Sie zwei Werte als Referenz übergeben, wobei einer von ihnen basierend auf einer Bedingung zurückgegeben wird:

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

Unsichere Ref-Operationen

In System.Runtime.CompilerServices.Unsafe ein Satz unsicherer Operationen definiert, mit denen Sie die ref Werte im Grunde wie Zeiger bearbeiten können.

Zum Beispiel, interpretieren Sie eine Speicheradresse ( ref ) als einen anderen Typ neu:

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

BitConverter.IsLittleEndian Sie dabei jedoch auf die Endianness , überprüfen BitConverter.IsLittleEndian z. B. BitConverter.IsLittleEndian und behandeln Sie es entsprechend.

Oder durchlaufen Sie ein Array auf unsichere Weise:

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

Oder das ähnliche 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);

Außerdem kann geprüft werden, ob zwei ref dieselbe Adresse haben, dh dieselbe Adresse:

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

Roslyn Github Ausgabe

System.Runtime.CompilerServices.Unsafe für github

Ausdrücke werfen

C # 7.0 erlaubt das Werfen als Ausdruck an bestimmten Stellen:

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

Wenn Sie vor C # 7.0 eine Ausnahme aus einem Ausdruckskörper auslösen möchten, müssen Sie Folgendes tun:

var spoons = "dinner,desert,soup".Split(',');

var spoonsArray = spoons.Length > 0 ? spoons : null;

if (spoonsArray == null) 
{
    throw new Exception("There are no spoons");
}

Oder

var spoonsArray = spoons.Length > 0 
    ? spoons 
    : new Func<string[]>(() => 
      {
          throw new Exception("There are no spoons");
      })();

In C # 7.0 ist das Obige nun vereinfacht zu:

var spoonsArray = spoons.Length > 0 ? spoons : throw new Exception("There are no spoons");

Liste der erweiterten Mitglieder mit Ausdruck

C # 7.0 fügt Accessoren, Konstruktoren und Finalisierer der Liste der Dinge hinzu, die Ausdruckskörper haben können:

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

Siehe auch den Abschnitt out var-Deklaration für den Discard-Operator.

ValueTask

Task<T> ist eine Klasse und verursacht den unnötigen Overhead ihrer Zuordnung, wenn das Ergebnis sofort verfügbar ist.

ValueTask<T> ist eine Struktur und wurde eingeführt, um die Zuweisung eines Task Objekts zu verhindern, falls das Ergebnis der asynchronen Operation zum Zeitpunkt des Warten bereits verfügbar ist.

ValueTask<T> bietet also zwei Vorteile:

1. Leistungssteigerung

Hier ist ein Task<T> Beispiel:

  • Erfordert eine Heapzuordnung
  • Nimmt 120s mit JIT
async Task<int> TestTask(int d)
{
    await Task.Delay(d);
    return 10;
}

Hier ist das analoge ValueTask<T> Beispiel:

  • Keine Heapzuordnung , wenn das Ergebnis synchron bekannt ist (was es wegen des in diesem Fall nicht ist Task.Delay , ist aber oft in vielen realen async / await Szenarien)
  • Nimmt 65s mit JIT
async ValueTask<int> TestValueTask(int d)
{
    await Task.Delay(d);
    return 10;
}

2. Erhöhte Flexibilität bei der Implementierung

Implementierungen einer asynchronen Schnittstelle, die synchron sein wollen, würden sonst gezwungen sein, entweder Task.Run oder Task.FromResult (was zu dem oben beschriebenen Performance- Task.FromResult führt). Daher besteht ein gewisser Druck gegenüber synchronen Implementierungen.

Mit ValueTask<T> können Implementierungen jedoch mehr frei wählen, ob sie synchron oder asynchron sind, ohne dass Anrufer beeinträchtigt werden.

Hier ist zum Beispiel eine Schnittstelle mit einer asynchronen Methode:

interface IFoo<T>
{
    ValueTask<T> BarAsync();
}

... und so könnte man diese Methode nennen:

IFoo<T> thing = getThing();
var x = await thing.BarAsync();

Mit ValueTask der obige Code entweder mit synchronen oder asynchronen Implementierungen :

Synchrone Implementierung:

class SynchronousFoo<T> : IFoo<T>
{
    public ValueTask<T> BarAsync()
    {
        var value = default(T);
        return new ValueTask<T>(value);
    }
}

Asynchrone Implementierung

class AsynchronousFoo<T> : IFoo<T>
{
    public async ValueTask<T> BarAsync()
    {
        var value = default(T);
        await Task.Delay(1);
        return value;
    }
}

Anmerkungen

Obwohl ValueTask wurde, dass ValueTask Struktur zu C # 7.0 hinzugefügt wurde, wurde sie vorerst als andere Bibliothek beibehalten. Das ValueTask <T> System.Threading.Tasks.Extensions Paket kann von der Nuget Gallery heruntergeladen werden



Modified text is an extract of the original Stack Overflow Documentation
Lizenziert unter CC BY-SA 3.0
Nicht angeschlossen an Stack Overflow