Szukaj…


Wprowadzenie

C # 7.0 to siódma wersja C #. Ta wersja zawiera kilka nowych funkcji: wsparcie językowe dla Tuples, funkcje lokalne, deklaracje out var , separatory cyfr, literały binarne, dopasowywanie wzorców, wyrażenia ref return i ref return ref local i rozszerzonych list członków wyrażających.

Oficjalna referencja: Co nowego w C # 7

nasza deklaracja var

Częstym wzorem w języku C # jest bool TryParse(object input, out object value) celu bezpiecznego parsowania obiektów.

Deklaracja out var to prosta funkcja poprawiająca czytelność. Pozwala zadeklarować zmienną w tym samym czasie, gdy jest przekazywana jako parametr wyjściowy.

Zmienna zadeklarowana w ten sposób ma zasięg do reszty ciała w punkcie, w którym została zadeklarowana.

Przykład

Używając TryParse przed C # 7.0, musisz zadeklarować zmienną, aby otrzymać wartość przed wywołaniem funkcji:

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

Foo(value); // ok

W C # 7.0 możesz wstawić deklarację zmiennej przekazywanej do parametru out , eliminując potrzebę oddzielnej deklaracji zmiennej:

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

Jeśli niektóre z parametrów, które są zwracane funkcyjne w out nie jest potrzebna można użyć operatora odrzuconych _ .

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

Deklaracja out var może być używana z dowolną istniejącą funkcją, która ma już parametry out . Składnia deklaracji funkcji pozostaje taka sama i nie są wymagane żadne dodatkowe wymagania, aby funkcja była zgodna z deklaracją out var . Ta funkcja to po prostu cukier składniowy.

Inną cechą deklaracji out var jest to, że można jej używać z typami anonimowymi.

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

W tym kodzie tworzymy Dictionary z kluczem int i tablicą o anonimowej wartości typu. W poprzedniej wersji C # nie było możliwe użycie tutaj metody TryGetValue , ponieważ wymagało to zadeklarowania zmiennej out (która jest typu anonimowego!). Jednak out var nie musimy jawnie określać typu zmiennej out .

Ograniczenia

Zauważ, że nasze deklaracje var mają ograniczone zastosowanie w zapytaniach LINQ, ponieważ wyrażenia są interpretowane jako ciała wyrażeń lambda, więc zakres wprowadzanych zmiennych jest ograniczony do tych lambd. Na przykład następujący kod nie będzie działać:

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

Bibliografia

Literały binarne

Przedrostek 0b może być użyty do przedstawienia literałów binarnych.

Literały binarne umożliwiają konstruowanie liczb z zer i jedynek, co znacznie ułatwia sprawdzenie, które bity są ustawione w binarnej reprezentacji liczby. Może to być przydatne do pracy z flagami binarnymi.

Poniżej przedstawione są sposoby określania int o wartości 34 (5 + 2 = 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

Wyliczenia flag

Wcześniej określenie wartości flagi dla enum można było wykonać tylko przy użyciu jednej z trzech metod w tym przykładzie:

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

W przypadku literałów binarnych bardziej oczywiste są ustawione bity, a ich użycie nie wymaga zrozumienia liczb szesnastkowych i arytmetyki bitowej:

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

Separatory cyfr

Podkreślenie _ może być użyte jako separator cyfr. Możliwość grupowania cyfr w duże literały liczbowe ma znaczący wpływ na czytelność.

Podkreślenie może występować w dowolnym miejscu literału numerycznego, z wyjątkiem przypadków wymienionych poniżej. Różne grupy mogą mieć sens w różnych scenariuszach lub przy różnych podstawach liczbowych.

Każda sekwencja cyfr może być oddzielona jednym lub kilkoma podkreślnikami. _ Jest dozwolone zarówno w liczbach dziesiętnych, jak i potęgach. Separatory nie mają wpływu semantycznego - są po prostu ignorowane.

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;

Gdzie _ cyfra separator nie może być stosowany:

  • na początku wartości ( _121 )
  • na końcu wartości ( 121_ lub 121.05_ )
  • obok dziesiętnego ( 10_.0 )
  • obok znaku wykładnika ( 1.1e_1 )
  • obok specyfikatora typu ( 10_f )
  • bezpośrednio po 0x lub 0b w literałach binarnych i szesnastkowych ( może być zmieniony, aby pozwolić np. 0b_1001_1000 )

Wsparcie językowe dla Tuples

Podstawy

Krotka to uporządkowana, skończona lista elementów. Krotki są powszechnie stosowane w programowaniu jako środek do pracy z jednym pojedynczym bytem zbiorowo zamiast osobnej pracy z każdym z elementów krotki oraz do reprezentowania poszczególnych wierszy (tj. „Rekordów”) w relacyjnej bazie danych.

W C # 7.0 metody mogą mieć wiele zwracanych wartości. Za kulisami kompilator użyje nowej struktury ValueTuple .

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

Uwaga dodatkowa : aby działało to w Visual Studio 2017, musisz uzyskać pakiet System.ValueTuple .

Jeśli wynik metody zwracającej krotkę jest przypisany do jednej zmiennej, możesz uzyskać dostęp do elementów członkowskich przez ich zdefiniowane nazwy na sygnaturze metody:

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

Dekonstrukcja krotowa

Dekonstrukcja krotek dzieli krotkę na części.

Na przykład wywołanie GetTallies i przypisanie wartości zwracanej do dwóch oddzielnych zmiennych dekonstruuje krotkę na te dwie zmienne:

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

var działa również:

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

Możesz także użyć krótszej składni, z var poza () :

var (s, c) = GetTallies();

Możesz także zdekonstruować na istniejące zmienne:

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

Zamiana jest teraz znacznie prostsza (nie jest wymagana zmienna temp):

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

Co ciekawe, dowolny obiekt można zdekonstruować, definiując metodę Deconstruct w klasie:

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;

W takim przypadku (localFirstName, localLastName) = person Deconstruct na person .

Dekonstrukcję można nawet zdefiniować w metodzie rozszerzenia. Jest to równoważne z powyższym:

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;

Alternatywnym podejściem dla klasy Person jest zdefiniowanie samej Name jako Tuple . Rozważ następujące:

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

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

Następnie możesz utworzyć taką osobę (gdzie możemy wziąć krotkę jako argument):

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

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

Inicjalizacja krotkowa

Możesz także dowolnie tworzyć krotki w kodzie:

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

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

Podczas tworzenia krotki możesz przypisać nazwy elementów ad hoc do członków krotki:

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

Wnioskowanie typu

Wiele krotek zdefiniowanych za pomocą tego samego podpisu (pasujące typy i liczba) będzie wywnioskowanych jako pasujące typy. Na przykład:

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 mogą być zwracane, ponieważ deklaracja zmiennej stats i sygnatura zwrotna metody są zgodne.

Refleksja i nazwy pól krotek

Nazwy członków nie istnieją w czasie wykonywania. Refleksja uwzględni krotki o tej samej liczbie i typach elementów takie same, nawet jeśli nazwy członków nie są zgodne. Konwersja krotki na object a następnie na krotkę z tymi samymi typami elementów, ale różnymi nazwami, również nie spowoduje wyjątku.

Chociaż sama klasa ValueTuple nie zachowuje informacji dla nazw członków, informacje są dostępne poprzez odbicie w TupleElementNamesAttribute. Ten atrybut nie jest stosowany do samej krotki, ale do parametrów metody, zwracanych wartości, właściwości i pól. Umożliwia to zachowanie nazw elementów krotkowych w zestawach, tzn. Jeśli metoda zwróci (nazwa ciągu, liczba całkowita) nazwy i liczba nazw będą dostępne dla wywołujących metodę w innym zestawie, ponieważ zwracana wartość zostanie oznaczona za pomocą TupleElementNameAttribute zawierającego wartości „name” i „count”.

Używaj z ogólnymi i async

Nowe funkcje krotki (wykorzystujące bazowy typ ValueTuple ) w pełni obsługują rodzaje ogólne i mogą być używane jako parametr typu ogólnego. Dzięki temu można ich używać z wzorcem async / await :

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

    return (fooBar, num);
}

Używaj z kolekcjami

Korzystne może być posiadanie kolekcji krotek w (na przykład) scenariuszu, w którym próbujesz znaleźć pasującą krotkę na podstawie warunków, aby uniknąć rozgałęzienia kodu.

Przykład:

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

Dzięki nowym krotkom mogą stać się:

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

Chociaż nazewnictwo w powyższej przykładowej krotce jest dość ogólne, idea odpowiednich etykiet pozwala na głębsze zrozumienie tego, co jest próbowane w kodzie, zamiast odwoływania się do „item1”, „item2” i „item3”.

Różnice między ValueTuple i Tuple

Głównym powodem wprowadzenia ValueTuple jest wydajność.

Wpisz imię ValueTuple Tuple
Klasa lub struktura struct class
Zmienność (zmiana wartości po utworzeniu) zmienny niezmienny
Nazewnictwo członków i obsługa innych języków tak nie ( TBD )

Bibliografia

Funkcje lokalne

Funkcje lokalne są zdefiniowane w ramach metody i nie są dostępne poza nią. Mają dostęp do wszystkich zmiennych lokalnych i obsługują iteratory, async / await i składnię lambda. W ten sposób powtórzenia specyficzne dla funkcji można sfunkcjonalizować bez zatłoczenia klasy. Jako efekt uboczny poprawia to wydajność sugestii inteligencji.

Przykład

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

Funkcje lokalne znacznie upraszczają kod dla operatorów LINQ, gdzie zwykle trzeba oddzielić kontrole argumentów od rzeczywistej logiki, aby kontrole argumentów były natychmiastowe, a nie opóźniane aż do rozpoczęcia iteracji.

Przykład

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

Funkcje lokalne obsługują również async i await słowa kluczowe.

Przykład

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

Jedną ważną rzeczą, którą mogłeś zauważyć, jest to, że funkcje lokalne można zdefiniować w instrukcji return , nie trzeba ich definiować powyżej. Ponadto funkcje lokalne zwykle są zgodne z konwencją nazewnictwa „lowerCamelCase”, aby łatwiej odróżnić się od funkcji zakresu klas.

Dopasowywanie wzorów

Rozszerzenia dopasowywania wzorców dla C # zapewniają wiele korzyści z dopasowywania wzorców z języków funkcjonalnych, ale w sposób płynnie integrujący się z wrażeniami języka bazowego

switch wyrażenie

Dopasowanie wzorca rozszerza instrukcję switch , aby włączyć typy:

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 ekspresja

Dopasowanie wzorca rozszerza operator is o sprawdzenie typu i zadeklarowanie nowej zmiennej w tym samym czasie.

Przykład

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

może być przepisany jako:

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

Zauważ również, że zakres zmiennej wzorca s jest rozszerzony na zewnątrz bloku if , który osiąga koniec obejmującego zakresu, przykład:

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 zwrot i ref lokalny

Zwroty referencyjne i locale referencyjne są przydatne do manipulowania i zwracania referencji do bloków pamięci zamiast kopiowania pamięci bez uciekania się do niebezpiecznych wskaźników.

Ref Return

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

Dzięki temu możesz przekazać dwie wartości przez odniesienie, a jedna z nich zostanie zwrócona na podstawie pewnych warunków:

Matrix3D left = …, right = …;
Choose(chooser, ref left, ref right).M20 = 1.0;

Ref Lokalny

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

Niebezpieczne operacje ref

W System.Runtime.CompilerServices.Unsafe zdefiniowano zestaw niebezpiecznych operacji, które pozwalają na manipulowanie wartościami ref tak, jakby były wskaźnikami.

Na przykład reinterpretacja adresu pamięci ( ref ) jako innego typu:

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

Wystrzegaj się jednak endianizmu , np. Sprawdź BitConverter.IsLittleEndian jeśli to konieczne i odpowiednio postępuj.

Lub iteruj tablicę w niebezpieczny sposób:

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

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

Dodatkowo można sprawdzić, czy dwa ref wartości są takie same, czyli ten sam adres:

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

Spinki do mankietów

Problem Roslyn Github

System.Runtime.CompilerServices.Unsafe na github

wyrzucać wyrażenia

C # 7.0 pozwala na rzucanie jako wyrażenie w niektórych miejscach:

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

Przed wersją C # 7.0, jeśli chcesz zgłosić wyjątek od treści wyrażenia, musisz:

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

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

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

Lub

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

W C # 7.0 powyższe jest teraz uproszczone do:

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

Rozszerzona lista członków treści

C # 7.0 dodaje akcesory, konstruktory i finalizatory do listy rzeczy, które mogą mieć ciała wyrażeń:

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

Zobacz także sekcję deklaracji out var dla operatora odrzuć.

ValueTask

Task<T> jest klasą i powoduje niepotrzebny narzut związany z jej alokacją, gdy wynik jest natychmiast dostępny.

ValueTask<T> to struktura, która została wprowadzona w celu zapobiegania alokacji obiektu Task w przypadku, gdy wynik operacji asynchronicznej jest już dostępny w momencie oczekiwania.

Dlatego ValueTask<T> zapewnia dwie korzyści:

1. Wzrost wydajności

Oto przykład Task<T> :

  • Wymaga przydziału sterty
  • Zajmuje 120ns z JIT
async Task<int> TestTask(int d)
{
    await Task.Delay(d);
    return 10;
}

Oto analogowy przykład ValueTask<T> :

  • Brak alokacji sterty, jeśli wynik jest znany synchronicznie (co nie jest w tym przypadku ze względu na Task.Delay , ale często występuje w wielu rzeczywistych scenariuszach async / await )
  • Zajmuje 65ns z JIT
async ValueTask<int> TestValueTask(int d)
{
    await Task.Delay(d);
    return 10;
}

2. Zwiększona elastyczność wdrażania

Implementacje interfejsu asynchronicznego, który chciałby być synchroniczny, byłyby w przeciwnym razie zmuszone do użycia Task.Run lub Task.FromResult (co skutkuje Task.FromResult wydajności omówionym powyżej). Dlatego istnieje pewna presja na synchroniczne implementacje.

Jednak dzięki ValueTask<T> implementacje mają większą swobodę wyboru między synchronizacją lub asynchronicznością bez wpływu na osoby dzwoniące.

Na przykład, oto interfejs z metodą asynchroniczną:

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

... a oto jak można wywołać tę metodę:

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

W przypadku ValueTask powyższy kod będzie działał z implementacjami synchronicznymi lub asynchronicznymi :

Implementacja synchroniczna:

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

Implementacja asynchroniczna

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

Notatki

Chociaż ValueTask struktury ValueTask do C # 7.0 , na razie jest ona przechowywana jako inna biblioteka. ValueTask <T> System.Threading.Tasks.Extensions pakiet można pobrać z Galerii Nuget



Modified text is an extract of the original Stack Overflow Documentation
Licencjonowany na podstawie CC BY-SA 3.0
Nie związany z Stack Overflow