C# Language
Funkcje C # 7.0
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:
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:
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.
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_
lub121.05_
) - obok dziesiętnego (
10_.0
) - obok znaku wykładnika (
1.1e_1
) - obok specyfikatora typu (
10_f
) - bezpośrednio po
0x
lub0b
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
- Oryginalna propozycja funkcji językowej Tuples na GitHub
- Uruchamialne rozwiązanie VS 15 dla funkcji C # 7.0
- Pakiet NuGet Tuple
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
string s = o as string;
if(s != null)
{
// do something with s
}
może być przepisany jako:
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
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 scenariuszachasync
/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