Recherche…


Introduction

C # 7.0 est la septième version de C #. Cette version contient quelques nouvelles fonctionnalités: prise en charge de la langue pour Tuples, fonctions locales, déclarations out var , séparateurs de chiffres, littéraux binaires, correspondance de modèle, expressions de jet, ref return ref local et liste de membres avec expression ref local et étendue.

Référence officielle: Quoi de neuf en C # 7

déclaration var out

Un modèle courant dans C # utilise bool TryParse(object input, out object value) pour analyser en toute sécurité des objets.

La déclaration out var est une fonctionnalité simple pour améliorer la lisibilité. Il permet à une variable d'être déclarée en même temps qu'elle est passée en paramètre out.

Une variable déclarée de cette façon est définie sur le reste du corps au moment où elle est déclarée.

Exemple

En utilisant TryParse avant C # 7.0, vous devez déclarer une variable pour recevoir la valeur avant d'appeler la fonction:

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

Foo(value); // ok

Dans C # 7.0, vous pouvez incorporer la déclaration de la variable transmise au paramètre out , ce qui élimine la nécessité d'une déclaration de variable distincte:

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

Si certains des paramètres qu'une fonction retourne en out n'est pas nécessaire , vous pouvez utiliser l'opérateur de défausse _ .

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

Une déclaration out var peut être utilisée avec toute fonction existante qui possède déjà out paramètres. La syntaxe de déclaration de la fonction reste la même et aucune exigence supplémentaire n'est requise pour rendre la fonction compatible avec une déclaration out var . Cette caractéristique est simplement du sucre syntaxique.

Une autre caractéristique de la déclaration out var est qu’elle peut être utilisée avec des types anonymes.

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

Dans ce code, nous créons un Dictionary avec la clé int et un tableau de valeur de type anonyme. Dans la version précédente de C #, il était impossible d'utiliser la méthode TryGetValue ici, car vous deviez déclarer la variable out (qui est de type anonyme!). Cependant, avec out var il n'est pas nécessaire de spécifier explicitement le type de la variable out .

Limites

Notez que les déclarations var sont d'une utilité limitée dans les requêtes LINQ car les expressions sont interprétées comme des corps d'expression lambda, de sorte que la portée des variables introduites est limitée à ces lambda. Par exemple, le code suivant ne fonctionnera pas:

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

Les références

Littéraux binaires

Le préfixe 0b peut être utilisé pour représenter les littéraux binaires.

Les littéraux binaires permettent de construire des nombres à partir de zéros et de uns, ce qui facilite la visualisation des bits définis dans la représentation binaire d'un nombre. Cela peut être utile pour travailler avec des indicateurs binaires.

Les méthodes suivantes permettent de spécifier un int avec la valeur 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

Énumérations de drapeaux

Avant, la spécification de valeurs d'indicateur pour un enum ne pouvait être effectuée qu'en utilisant l'une des trois méthodes de cet exemple:

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

Avec les littéraux binaires, il est plus clair quels bits sont définis et leur utilisation ne nécessite pas de comprendre les nombres hexadécimaux et l'arithmétique binaire:

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

Séparateurs de chiffres

Le trait de soulignement _ peut être utilisé comme séparateur de chiffres. Pouvoir regrouper des chiffres dans de grands littéraux numériques a un impact significatif sur la lisibilité.

Le trait de soulignement peut apparaître n'importe où dans un littéral numérique, sauf comme indiqué ci-dessous. Des regroupements différents peuvent avoir un sens dans différents scénarios ou avec des bases numériques différentes.

Toute séquence de chiffres peut être séparée par un ou plusieurs traits de soulignement. Le _ est autorisé dans les décimales ainsi que les exposants. Les séparateurs n'ont aucun impact sémantique - ils sont simplement ignorés.

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;

Lorsque le _ séparateur de chiffres ne peut pas être utilisé:

  • au début de la valeur ( _121 )
  • à la fin de la valeur ( 121_ ou 121.05_ )
  • à côté de la décimale ( 10_.0 )
  • à côté du caractère de l'exposant ( 1.1e_1 )
  • à côté du spécificateur de type ( 10_f )
  • immédiatement après le 0x ou 0b dans les littéraux binaires et hexadécimaux ( peut être modifié pour permettre par exemple 0b_1001_1000 )

Prise en charge linguistique pour Tuples

Les bases

Un tuple est une liste ordonnée et finie d'éléments. Les tuples sont couramment utilisés en programmation comme moyen de travailler collectivement avec une seule entité au lieu de travailler individuellement avec chacun des éléments du tuple et de représenter des lignes individuelles (par exemple des "enregistrements") dans une base de données relationnelle.

En C # 7.0, les méthodes peuvent avoir plusieurs valeurs de retour. Dans les coulisses, le compilateur utilisera la nouvelle structure ValueTuple .

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

Remarque : pour que cela fonctionne dans Visual Studio 2017, vous devez obtenir le package System.ValueTuple .

Si un résultat de méthode retournant un tuple est affecté à une seule variable, vous pouvez accéder aux membres par leurs noms définis sur la signature de la méthode:

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

Déconstruction de Tuple

La déconstruction des tuples sépare un tuple en ses parties.

Par exemple, l'appel de GetTallies et l'affectation de la valeur de retour à deux variables distinctes déconstruit le tuple en ces deux variables:

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

var fonctionne aussi:

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

Vous pouvez également utiliser une syntaxe plus courte, avec var dehors de () :

var (s, c) = GetTallies();

Vous pouvez également décomposer en variables existantes:

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

L'échange est maintenant beaucoup plus simple (pas de variable temporaire nécessaire):

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

Fait intéressant, tout objet peut être déconstruit en définissant une méthode Deconstruct dans la classe:

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;

Dans ce cas, la (localFirstName, localLastName) = person appelle Deconstruct sur la person .

La déconstruction peut même être définie dans une méthode d'extension. Ceci est équivalent à ce qui précède:

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;

Une approche alternative pour la classe Person est de définir le Name lui-même comme un Tuple . Considérer ce qui suit:

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

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

Ensuite, vous pouvez instancier une personne comme ça (où nous pouvons prendre un tuple comme argument):

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

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

Initialisation du tuple

Vous pouvez également créer arbitrairement des tuples dans le code:

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

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

Lors de la création d'un tuple, vous pouvez attribuer des noms d'élément ad hoc aux membres du tuple:

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

Type d'inférence

Plusieurs tuples définis avec la même signature (types et nombre correspondants) seront déduits comme types correspondants. Par exemple:

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 peuvent être retournées car la déclaration de la variable stats et la signature de retour de la méthode correspondent.

Noms de champ de réflexion et de tuple

Les noms de membres n'existent pas à l'exécution. Reflection considérera les tuples avec le même nombre et les mêmes types de membres, même si les noms des membres ne correspondent pas. La conversion d'un tuple en object , puis en un tuple avec les mêmes types de membres, mais avec des noms différents, ne provoquera pas non plus d'exception.

Alors que la classe ValueTuple elle-même ne conserve pas les informations pour les noms de membres, les informations sont disponibles par réflexion dans un TupleElementNamesAttribute. Cet attribut n'est pas appliqué au tuple lui-même mais aux paramètres de méthode, aux valeurs de retour, aux propriétés et aux champs. Cela permet de conserver les noms d'élément de tuple dans les assemblages, c'est-à-dire que si une méthode retourne (nom de chaîne, int nombre), les noms et les noms seront disponibles pour les appelants de la méthode. "nom" et "compte".

Utiliser avec des génériques et async

Les nouvelles fonctionnalités de tuple (utilisant le type ValueTuple sous-jacent) prennent entièrement en charge les génériques et peuvent être utilisées comme paramètre de type générique. Cela permet de les utiliser avec le modèle async / await :

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

    return (fooBar, num);
}

Utiliser avec des collections

Il peut être avantageux d'avoir une collection de tuples dans (par exemple) un scénario où vous essayez de trouver un tuple correspondant aux conditions pour éviter le branchement du code.

Exemple:

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

Avec les nouveaux tuples peuvent devenir:

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

Bien que le nom donné à l'exemple ci-dessus soit assez générique, l'idée d'étiquettes pertinentes permet une meilleure compréhension de ce qui est tenté dans le code par rapport à "item1", "item2" et "item3".

Différences entre ValueTuple et Tuple

La principale raison de l'introduction de ValueTuple est la performance.

Nom du type ValueTuple Tuple
Classe ou structure struct class
Mutabilité (changer les valeurs après la création) mutable immuable
Nommer les membres et autre support linguistique Oui non (à déterminer )

Les références

Fonctions locales

Les fonctions locales sont définies dans une méthode et ne sont pas disponibles en dehors de celle-ci. Ils ont accès à toutes les variables locales et prennent en charge les itérateurs, la syntaxe async / await et lambda. De cette façon, les répétitions spécifiques à une fonction peuvent être fonctionnalisées sans surcharger la classe. Comme effet secondaire, cela améliore la performance de la suggestion intellisense.

Exemple

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

Les fonctions locales simplifient considérablement le code pour les opérateurs LINQ, où vous devez généralement séparer les vérifications d’argument de la logique réelle pour que les vérifications d’argument soient instantanées, et ne soient retardées qu’après le démarrage de l’itération.

Exemple

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

Les fonctions locales prennent également en charge l' async et await mots-clés.

Exemple

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

Une chose importante que vous avez peut-être remarquée est que les fonctions locales peuvent être définies dans la déclaration de return , elles ne doivent pas nécessairement être définies au-dessus. De plus, les fonctions locales suivent généralement la convention de dénomination "lowerCamelCase" pour se différencier plus facilement des fonctions de portée de classe.

Correspondance de motif

Les extensions de correspondance de modèles pour C # permettent de nombreux avantages de la correspondance de modèles à partir de langages fonctionnels, mais d'une manière qui s'intègre harmonieusement à la convivialité du langage sous-jacent

switch expression

La correspondance de motifs étend l' switch déclaration pour allumer types:

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 expression

La correspondance de modèle étend l'opérateur is pour vérifier un type et déclare une nouvelle variable en même temps.

Exemple

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

peut être réécrit comme suit:

7.0
if(o is string s)
{
    //Do something with s
};

Notez également que la portée de la variable de modèle s est étendue en dehors du bloc if atteignant la fin de la portée englobante, par exemple:

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 retour et ref local

Les retours Ref et les sections locales de référence sont utiles pour manipuler et renvoyer des références à des blocs de mémoire au lieu de copier la mémoire sans avoir recours à des pointeurs non sécurisés.

Ref Return

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

Avec cela, vous pouvez passer deux valeurs par référence, l'une d'entre elles étant renvoyée en fonction de certaines conditions:

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

Opérations de référence non sécurisées

Dans System.Runtime.CompilerServices.Unsafe un ensemble d'opérations dangereuses ont été définies pour vous permettre de manipuler les valeurs ref comme s'il s'agissait de pointeurs.

Par exemple, réinterpréter une adresse mémoire ( ref ) sous un autre type:

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

Faites attention à la finalité lorsque vous faites cela, cependant, par exemple, vérifiez BitConverter.IsLittleEndian si nécessaire et gérez en conséquence.

Ou itérer sur un tableau d'une manière dangereuse:

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

Ou la Subtract similaire:

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

De plus, on peut vérifier si deux valeurs ref sont identiques, c’est-à-dire même 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]));

Liens

Question de Roslyn Github

System.Runtime.CompilerServices.Unsafe sur github

jeter des expressions

C # 7.0 permet de lancer comme expression à certains endroits:

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

Avant C # 7.0, si vous voulez lancer une exception à partir d'un corps d'expression, vous devez:

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

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

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

Ou

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

En C # 7.0, ce qui précède est maintenant simplifié pour:

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

Expression étendue liste des membres corporels

C # 7.0 ajoute des accesseurs, des constructeurs et des finaliseurs à la liste des éléments pouvant avoir des corps d’expression:

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

Voir également la section de déclaration out var pour l'opérateur de suppression.

ValueTask

Task<T> est une classe et entraîne la surcharge inutile de son allocation lorsque le résultat est immédiatement disponible.

ValueTask<T> est une structure qui a été introduite pour empêcher l'allocation d'un objet Task au cas où le résultat de l'opération asynchrone est déjà disponible au moment de l'attente.

Donc, ValueTask<T> offre deux avantages:

1. Augmentation de la performance

Voici un exemple de Task<T> :

  • Nécessite une allocation de tas
  • Prend 120ns avec JIT
async Task<int> TestTask(int d)
{
    await Task.Delay(d);
    return 10;
}

Voici l'exemple ValueTask<T> analogique ValueTask<T> :

  • Pas d'allocation de tas si le résultat est connu de manière synchrone (ce qui n'est pas le cas à cause de Task.Delay , mais se trouve souvent dans de nombreux scénarios async / d' await réels)
  • Prend 65ns avec JIT
async ValueTask<int> TestValueTask(int d)
{
    await Task.Delay(d);
    return 10;
}

2. Flexibilité accrue de la mise en œuvre

Les implémentations d'une interface asynchrone souhaitant être synchrone seraient sinon obligées d'utiliser Task.Run ou Task.FromResult (ce qui entraînerait une pénalité de performance discutée ci-dessus). Il y a donc une certaine pression contre les implémentations synchrones.

Mais avec ValueTask<T> , les implémentations sont plus libres de choisir entre être synchrones ou asynchrones sans affecter les appelants.

Par exemple, voici une interface avec une méthode asynchrone:

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

... et voici comment cette méthode pourrait s'appeler:

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

Avec ValueTask , le code ci-dessus fonctionnera avec des implémentations synchrones ou asynchrones :

Implémentation synchrone:

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

Implémentation asynchrone

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

Remarques

Bien que la structure ValueTask ait été prévue pour être ajoutée à C # 7.0 , elle a été conservée comme une autre bibliothèque pour le moment. Le package ValueTask <T> System.Threading.Tasks.Extensions peut être téléchargé à partir de Nuget Gallery



Modified text is an extract of the original Stack Overflow Documentation
Sous licence CC BY-SA 3.0
Non affilié à Stack Overflow