C# Language
C # 7.0 Kenmerken
Zoeken…
Invoering
C # 7.0 is de zevende versie van C #. Deze versie bevat enkele nieuwe functies: taalondersteuning voor Tuples, lokale functies, out var
declaraties, cijferscheidingstekens, binaire literalen, patroonovereenkomst, gooi-expressies, ref return
en ref local
en uitgebreide ledenlijst met fysieke leden.
Officiële referentie: wat is nieuw in C # 7
var-verklaring
Een gebruikelijk patroon in C # is het gebruik van bool TryParse(object input, out object value)
om objecten veilig te ontleden.
De out var
aangifte is een eenvoudige functie om de leesbaarheid te verbeteren. Hiermee kan een variabele worden gedeclareerd terwijl deze wordt doorgegeven als een parameter out.
Een variabele die op deze manier wordt gedeclareerd, is gericht op de rest van het lichaam op het punt waarop deze wordt gedeclareerd.
Voorbeeld
Als u TryParse
vóór C # 7.0 gebruikt, moet u een variabele declareren om de waarde te ontvangen voordat u de functie TryParse
:
int value;
if (int.TryParse(input, out value))
{
Foo(value); // ok
}
else
{
Foo(value); // value is zero
}
Foo(value); // ok
In C # 7.0 kunt u de declaratie van de variabele doorgeven die aan de parameter out
doorgegeven, waardoor de noodzaak voor een afzonderlijke variabele declaratie is geëlimineerd:
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
Als sommige van de parameters die een functie terugkeert in out
niet nodig is kunt u het zich ontdoen van operator te gebruiken _
.
p.GetCoordinates(out var x, out _); // I only care about x
Een out var
verklaring kan worden gebruikt met bestaande functie die reeds out
parameters. De syntaxis van de functieverklaring blijft hetzelfde en er zijn geen aanvullende vereisten nodig om de functie compatibel te maken met een out var
aangifte. Deze functie is gewoon syntactische suiker.
Een ander kenmerk van onze out var
verklaring is dat het kan worden gebruikt met anonieme typen.
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 deze code maken we een Dictionary
met int
sleutel en een reeks anonieme typewaarden. In de vorige versie van C # was het onmogelijk om de TryGetValue
methode hier te gebruiken, omdat je de variabele out
moest declareren (die van het anonieme type is!). Zonder out var
hoeven we echter niet expliciet het type van de out
variabele te specificeren.
beperkingen
Merk op dat onze var-verklaringen van beperkt nut zijn in LINQ-query's omdat expressies worden geïnterpreteerd als expressie-lambda-instanties, dus het bereik van de geïntroduceerde variabelen is beperkt tot deze lambdas. De volgende code werkt bijvoorbeeld niet:
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
Referenties
Binaire literals
Het voorvoegsel 0b kan worden gebruikt om binaire literalen voor te stellen.
Met binaire literalen kunnen getallen uit nullen en enen worden geconstrueerd, wat het gemakkelijker maakt om te zien welke bits zijn ingesteld in de binaire weergave van een getal. Dit kan handig zijn voor het werken met binaire vlaggen.
De volgende zijn equivalente manieren om een int
met waarde 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
Vlaggen opsommingen
Voorheen kon het opgeven van vlagwaarden voor een enum
alleen worden gedaan met behulp van een van de drie methoden in dit voorbeeld:
[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
}
Met binaire literalen is het duidelijker welke bits zijn ingesteld, en het gebruik ervan vereist geen begrip van hexadecimale getallen en bitgewijze rekenkundige:
[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
}
Cijfer scheidingstekens
Het onderstrepingsteken _
kan worden gebruikt als cijferscheidingsteken. Het kunnen groeperen van cijfers in grote numerieke letterlijke letters heeft een aanzienlijke invloed op de leesbaarheid.
Het onderstrepingsteken kan overal in een numerieke letter voorkomen, behalve zoals hieronder aangegeven. Verschillende groepen kunnen zinvol zijn in verschillende scenario's of met verschillende numerieke basissen.
Elke reeks cijfers kan worden gescheiden door een of meer onderstrepingstekens. De _
is toegestaan in decimalen en in exponenten. De scheidingstekens hebben geen semantische impact - ze worden gewoon genegeerd.
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;
Waar het scheidingsteken _
niet mag worden gebruikt:
- aan het begin van de waarde (
_121
) - aan het einde van de waarde (
121_
of121.05_
) - naast de komma (
10_.0
) - naast het exponent-teken (
1.1e_1
) - naast de typespecificatie (
10_f
) - onmiddellijk volgend op de
0x
of0b
in binaire en hexadecimale letterlijke waarden ( kan worden gewijzigd om bijvoorbeeld 0b_1001_1000 toe te staan )
Taalondersteuning voor Tuples
Basics
Een tuple is een geordende, eindige lijst met elementen. Tuples worden gewoonlijk gebruikt bij het programmeren als een middel om samen met één enkele entiteit te werken in plaats van individueel met elk van de elementen van de tuple te werken, en om individuele rijen (dwz "records") in een relationele database weer te geven.
In C # 7.0 kunnen methoden meerdere retourwaarden hebben. Achter de schermen gebruikt de compiler de nieuwe ValueTuple- structuur.
public (int sum, int count) GetTallies()
{
return (1, 2);
}
Kanttekening: om dit te laten werken in Visual Studio 2017, moet u het krijgen System.ValueTuple
pakket.
Als een resultaat met tuple-terugkerende methode is toegewezen aan een enkele variabele, hebt u toegang tot de leden met hun gedefinieerde namen op de handtekening van de methode:
var result = GetTallies();
// > result.sum
// 1
// > result.count
// 2
Tuple-deconstructie
Tuple-deconstructie scheidt een tuple in zijn delen.
Als u bijvoorbeeld GetTallies
en de retourwaarde toewijst aan twee afzonderlijke variabelen, wordt de tuple gedeconstrueerd in die twee variabelen:
(int tallyOne, int tallyTwo) = GetTallies();
var
werkt ook:
(var s, var c) = GetTallies();
Je kunt ook een kortere syntaxis gebruiken, met var
buiten ()
:
var (s, c) = GetTallies();
Je kunt ook deconstrueren in bestaande variabelen:
int s, c;
(s, c) = GetTallies();
Ruilen is nu veel eenvoudiger (geen tijdelijke variabele vereist):
(b, a) = (a, b);
Interessant is dat elk object kan worden gedeconstrueerd door een Deconstruct
methode in de klasse te definiëren:
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 dit geval (localFirstName, localLastName) = person
syntaxis van (localFirstName, localLastName) = person
Deconstruct
op de person
.
Deconstructie kan zelfs worden gedefinieerd in een uitbreidingsmethode. Dit komt overeen met het bovenstaande:
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;
Een alternatieve benadering voor de klasse Person
is om de Name
zelf te definiëren als een Tuple
. Stel je de volgende situatie voor:
class Person
{
public (string First, string Last) Name { get; }
public Person((string FirstName, string LastName) name)
{
Name = name;
}
}
Dan kun je een persoon als volgt instantiëren (waar we een tuple als argument kunnen nemen):
var person = new Person(("Jane", "Smith"));
var firstName = person.Name.First; // "Jane"
var lastName = person.Name.Last; // "Smith"
Tuple-initialisatie
Je kunt ook willekeurig tupels in code maken:
var name = ("John", "Smith");
Console.WriteLine(name.Item1);
// Outputs John
Console.WriteLine(name.Item2);
// Outputs Smith
Bij het maken van een tuple, kunt u ad-hoc itemnamen toewijzen aan de leden van de tuple:
var name = (first: "John", middle: "Q", last: "Smith");
Console.WriteLine(name.first);
// Outputs John
Type inferentie
Meerdere tupels gedefinieerd met dezelfde handtekening (matching types en count) zullen worden afgeleid als matching types. Bijvoorbeeld:
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
kunnen worden geretourneerd omdat de verklaring van de stats
en de retoursignatuur van de methode overeenkomen.
Reflectie en Tuple veldnamen
Ledennamen bestaan niet tijdens runtime. Reflectie beschouwt tuples met hetzelfde aantal en hetzelfde soort leden, zelfs als de namen van de leden niet overeenkomen. Het converteren van een tuple naar een object
en vervolgens naar een tuple met dezelfde lidtypen, maar met verschillende namen, zal ook geen uitzondering veroorzaken.
Hoewel de ValueTuple-klasse zelf geen informatie voor ledennamen bewaart, is de informatie beschikbaar via reflectie in een TupleElementNamesAttribute. Dit kenmerk wordt niet toegepast op de tuple zelf, maar op methodeparameters, retourwaarden, eigenschappen en velden. Dit maakt het mogelijk om tuple itemnamen te behouden in assemblies, dwz als een methode retourneert (stringnaam, int count), zullen de naamnaam en het aantal beschikbaar zijn voor aanroepen van de methode in een andere assembly omdat de retourwaarde wordt gemarkeerd met TupleElementNameAttribute dat de waarden bevat "name" en "count".
Gebruik met generieke geneesmiddelen en async
De nieuwe tuple-functies (met behulp van het onderliggende ValueTuple
type) ondersteunen generieke geneesmiddelen volledig en kunnen worden gebruikt als generieke parameter. Dat maakt het mogelijk om ze te gebruiken met het async
/ await
patroon:
public async Task<(string value, int count)> GetValueAsync()
{
string fooBar = await _stackoverflow.GetStringAsync();
int num = await _stackoverflow.GetIntAsync();
return (fooBar, num);
}
Gebruik met collecties
Het kan handig zijn om een verzameling tupels te hebben in (een voorbeeld) een scenario waarin u probeert een overeenkomende tupel te vinden op basis van voorwaarden om vertakking van de code te voorkomen.
Voorbeeld:
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;
}
Met de nieuwe tupels kan worden:
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;
}
Hoewel de naamgeving in het bovenstaande voorbeeld tuple vrij generiek is, zorgt het idee van relevante labels voor een beter begrip van wat er wordt geprobeerd in de code boven "item1", "item2" en "item3".
Verschillen tussen ValueTuple en Tuple
De belangrijkste reden voor de introductie van ValueTuple
is prestaties.
Typ de naam | ValueTuple | Tuple |
---|---|---|
Klasse of structuur | struct | class |
Mutabiliteit (waarden veranderen na creatie) | veranderlijk | onveranderlijk |
Leden benoemen en ondersteuning voor andere talen | Ja | nee ( TBD ) |
Referenties
- Origineel taalvoorstel Tuples op GitHub
- Een uitvoerbare VS 15-oplossing voor C # 7.0-functies
- NuGet Tuple-pakket
Lokale functies
Lokale functies worden binnen een methode gedefinieerd en zijn daarbuiten niet beschikbaar. Ze hebben toegang tot alle lokale variabelen en ondersteunen iterators, async
/ await
en lambda-syntaxis. Op deze manier kunnen herhalingen die specifiek zijn voor een functie worden gefunctionaliseerd zonder de klas te verdringen. Als bijwerking verbetert dit de prestaties van de suggestie van intellisense.
Voorbeeld
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 functies vereenvoudigen de code aanzienlijk voor LINQ-operatoren, waar u meestal argumentcontroles moet scheiden van de werkelijke logica om argumentcontroles direct te maken, niet vertraagd tot nadat de iteratie is gestart.
Voorbeeld
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 functies ondersteunen ook de async
en await
trefwoorden.
Voorbeeld
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);
}
}
}
}
Een belangrijk ding dat u misschien is opgevallen, is dat lokale functies kunnen worden gedefinieerd onder de return
instructie, ze hoeven er niet boven te worden gedefinieerd. Bovendien volgen lokale functies doorgaans de naamgevingsconventie "lowerCamelCase" om zich gemakkelijker te onderscheiden van functies van klassebereik.
Patroon matching
Patroonaanpassingsuitbreidingen voor C # maken veel van de voordelen van patroonafstemming uit functionele talen mogelijk, maar op een manier die soepel integreert met het gevoel van de onderliggende taal
switch
uitdrukking
Patroonaanpassing breidt de switch
om typen in te schakelen:
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
uitdrukking
Patroonherkenning breidt de is
operator te controleren op het type en verklaren een nieuwe variabele tegelijkertijd.
Voorbeeld
string s = o as string;
if(s != null)
{
// do something with s
}
kan worden herschreven als:
if(o is string s)
{
//Do something with s
};
Merk ook op dat het bereik van de patroonvariabele s
wordt uitgebreid tot buiten het if
blok dat het einde van het omsluitende bereik bereikt, bijvoorbeeld:
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 en ref lokaal
Ref-retouren en ref-locals zijn handig voor het manipuleren en retourneren van verwijzingen naar geheugenblokken in plaats van geheugen te kopiëren zonder toevlucht te nemen tot onveilige verwijzingen.
Ref Return
public static ref TValue Choose<TValue>(
Func<bool> condition, ref TValue left, ref TValue right)
{
return condition() ? ref left : ref right;
}
Hiermee kunt u twee waarden doorgeven door middel van referentie, waarvan er één wordt geretourneerd op basis van een voorwaarde:
Matrix3D left = …, right = …;
Choose(chooser, ref left, ref right).M20 = 1.0;
Ref Lokaal
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
Onveilige ref-operaties
In System.Runtime.CompilerServices.Unsafe
een set onveilige bewerkingen gedefinieerd waarmee u ref
waarden kunt manipuleren alsof het in feite pointers waren.
Bijvoorbeeld, een geheugenadres ( ref
) opnieuw interpreteren als een ander 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]);
Let echter op endianness wanneer u dit doet, bijv. Controleer BitConverter.IsLittleEndian
indien nodig en BitConverter.IsLittleEndian
dienovereenkomstig.
Of doorzoek een array op een onveilige manier:
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);
Of de soortgelijke 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);
Bovendien kan worden gecontroleerd of twee ref
waarden hetzelfde zijn, dwz hetzelfde 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]));
Links
System.Runtime.CompilerServices.Unsafe op github
gooi uitdrukkingen
Met C # 7.0 kan op bepaalde plaatsen worden gegooid:
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();
}
Voorafgaand aan C # 7.0, als u een uitzondering van een expressie-instantie wilde weggooien, zou u:
var spoons = "dinner,desert,soup".Split(',');
var spoonsArray = spoons.Length > 0 ? spoons : null;
if (spoonsArray == null)
{
throw new Exception("There are no spoons");
}
Of
var spoonsArray = spoons.Length > 0
? spoons
: new Func<string[]>(() =>
{
throw new Exception("There are no spoons");
})();
In C # 7.0 is het bovenstaande nu vereenvoudigd tot:
var spoonsArray = spoons.Length > 0 ? spoons : throw new Exception("There are no spoons");
Uitgebreide uitgebreide ledenlijst met expressie
C # 7.0 voegt accessors, constructors en finalizers toe aan de lijst met dingen die expressie-instanties kunnen hebben:
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
}
}
Zie ook het gedeelte over de var-aangifte voor de verwijderingsoperator.
ValueTask
Task<T>
is een klasse en veroorzaakt de onnodige overhead van de toewijzing wanneer het resultaat onmiddellijk beschikbaar is.
ValueTask<T>
is een structuur en is ingevoerd om de toewijzing van een voorkomen Task
object bij het resultaat van de asynchrone werking reeds op het tijdstip van wachten.
Dus ValueTask<T>
biedt twee voordelen:
1. Prestatieverbetering
Hier is een voorbeeld van een Task<T>
:
- Vereist heap-toewijzing
- Duurt 120ns met JIT
async Task<int> TestTask(int d)
{
await Task.Delay(d);
return 10;
}
Hier is het analoge ValueTask<T>
:
- Geen heap-toewijzing als het resultaat synchroon bekend is (wat in dit geval niet het geval is vanwege de
Task.Delay
, maar vaak in veel real-worldasync
/await
scenario's) - Duurt 65ns met JIT
async ValueTask<int> TestValueTask(int d)
{
await Task.Delay(d);
return 10;
}
2. Verhoogde implementatieflexibiliteit
Implementaties van een async-interface die synchroon wenst te zijn, zouden anders gedwongen zijn Task.Run
of Task.FromResult
(resulterend in de hierboven beschreven prestatieboete). Er is dus enige druk tegen synchrone implementaties.
Maar met ValueTask<T>
zijn implementaties ValueTask<T>
om te kiezen tussen synchroon of asynchroon zonder bellers te beïnvloeden.
Hier is bijvoorbeeld een interface met een asynchrone methode:
interface IFoo<T>
{
ValueTask<T> BarAsync();
}
... en hier is hoe die methode zou kunnen worden genoemd:
IFoo<T> thing = getThing();
var x = await thing.BarAsync();
Met ValueTask
werkt de bovenstaande code met synchrone of asynchrone implementaties :
Synchrone implementatie:
class SynchronousFoo<T> : IFoo<T>
{
public ValueTask<T> BarAsync()
{
var value = default(T);
return new ValueTask<T>(value);
}
}
Asynchrone implementatie
class AsynchronousFoo<T> : IFoo<T>
{
public async ValueTask<T> BarAsync()
{
var value = default(T);
await Task.Delay(1);
return value;
}
}
Notes
Hoewel ValueTask
struct was gepland om te worden toegevoegd aan C # 7.0 , is het voorlopig als een andere bibliotheek bewaard. ValueTask <T> System.Threading.Tasks.Extensions
pakket kan worden gedownload van Nuget Gallery