Nuta
Dostęp do tej strony wymaga autoryzacji. Możesz spróbować zalogować się lub zmienić katalogi.
Dostęp do tej strony wymaga autoryzacji. Możesz spróbować zmienić katalogi.
Notatka
Ten artykuł jest specyfikacją funkcji. Specyfikacja służy jako dokument projektowy dla funkcji. Zawiera proponowane zmiany specyfikacji wraz z informacjami wymaganymi podczas projektowania i opracowywania funkcji. Te artykuły są publikowane do momentu sfinalizowania proponowanych zmian specyfikacji i włączenia ich do obecnej specyfikacji ECMA.
Mogą wystąpić pewne rozbieżności między specyfikacją funkcji a ukończoną implementacją. Te różnice są przechwytywane w odpowiednich spotkania projektowego języka (LDM).
Więcej informacji na temat procesu wdrażania specyfikacji funkcji można znaleźć w standardzie języka C# w artykule dotyczącym specyfikacji .
Kwestia związana z mistrzem: https://github.com/dotnet/csharplang/issues/4934
Streszczenie
Proponowane zmiany:
- Zezwalaj na wyrażenia lambda z atrybutami
- Zezwalaj na wyrażenia lambda z jawnym typem zwrotnym
- Ustalanie naturalnego typu delegata dla lambd i grup metod
Motywacja
Obsługa atrybutów w wyrażeniach lambda zapewnia równoważność metod i funkcji lokalnych.
Obsługa jawnych typów zwracanych zapewnia symetrię z parametrami lambda, w których można określić jawne typy. Zezwolenie na stosowanie jawnych typów zwracanych dałoby również kontrolę nad wydajnością kompilatora w zagnieżdżonych lambdach, gdzie rozwiązywanie przeciążenia obecnie musi wiązać ciało lambdy, aby określić sygnaturę.
Typ naturalny dla wyrażeń lambda i grup metod umożliwi więcej scenariuszy, w których wyrażenia lambda i grupy metod mogą być używane bez jawnego typu delegata, w tym jako inicjatory w deklaracjach var.
Wymaganie jawnych typów delegatów dla lambd i grup metod jest punktem konfliktowym dla klientów i stało się utrudnieniem w postępach ASP.NET przy ostatnich pracach nad MapAction.
ASP.NET MapAction bez zmian (MapAction() przyjmuje argument System.Delegate):
[HttpGet("/")] Todo GetTodo() => new(Id: 0, Name: "Name");
app.MapAction((Func<Todo>)GetTodo);
[HttpPost("/")] Todo PostTodo([FromBody] Todo todo) => todo;
app.MapAction((Func<Todo, Todo>)PostTodo);
ASP.NET MapAction z typami naturalnymi dla grup metod:
[HttpGet("/")] Todo GetTodo() => new(Id: 0, Name: "Name");
app.MapAction(GetTodo);
[HttpPost("/")] Todo PostTodo([FromBody] Todo todo) => todo;
app.MapAction(PostTodo);
ASP.NET MapAction z atrybutami i typami naturalnymi dla wyrażeń lambda:
app.MapAction([HttpGet("/")] () => new Todo(Id: 0, Name: "Name"));
app.MapAction([HttpPost("/")] ([FromBody] Todo todo) => todo);
Atrybuty
Atrybuty można dodać do wyrażeń lambda i parametrów lambda. Aby uniknąć niejednoznaczności między atrybutami metody a atrybutami parametrów, wyrażenie lambda z atrybutami musi używać listy parametrów w nawiasach. Typy parametrów nie są wymagane.
f = [A] () => { }; // [A] lambda
f = [return:A] x => x; // syntax error at '=>'
f = [return:A] (x) => x; // [A] lambda
f = [A] static x => x; // syntax error at '=>'
f = ([A] x) => x; // [A] x
f = ([A] ref int x) => x; // [A] x
Można określić wiele atrybutów rozdzielonych przecinkami na tej samej liście atrybutów lub jako oddzielne listy atrybutów.
var f = [A1, A2][A3] () => { }; // ok
var g = ([A1][A2, A3] int x) => x; // ok
Atrybuty nie są obsługiwane w przypadku metod anonimowych zadeklarowanych za pomocą składni delegate { }.
f = [A] delegate { return 1; }; // syntax error at 'delegate'
f = delegate ([A] int x) { return x; }; // syntax error at '['
Analizator będzie przeglądać kod, aby odróżnić inicjator kolekcji z przypisaniem elementu od inicjatora kolekcji z wyrażeniem lambda.
var y = new C { [A] = x }; // ok: y[A] = x
var z = new C { [A] x => x }; // ok: z[0] = [A] x => x
Analizator będzie traktować ?[ jako początek dostępu do elementu warunkowego.
x = b ? [A]; // ok
y = b ? [A] () => { } : z; // syntax error at '('
Atrybuty w wyrażeniu lambda lub parametrach lambda będą emitowane do metadanych w metodzie mapowanej na lambda.
Ogólnie rzecz biorąc, klienci nie powinni zależeć od sposobu mapowania wyrażeń lambda i funkcji lokalnych ze źródła na metadane. Sposób emitowania funkcji lambda i funkcji lokalnych może i zmienia się między wersjami kompilatora.
Proponowane tutaj zmiany dotyczą scenariusza opartego na Delegate.
Powinno być możliwe sprawdzenie MethodInfo skojarzonego z wystąpieniem Delegate, aby określić podpis wyrażenia lambda lub funkcji lokalnej, w tym atrybutów jawnych i dodatkowych metadanych generowanych przez kompilator, takich jak parametry domyślne.
Dzięki temu zespoły, takie jak ASP.NET, udostępniają te same zachowania dla lambd i funkcji lokalnych, co zwykłe metody.
Wyraźny typ zwracany
Przed ujętą w nawiasy listą parametrów można określić jawny typ zwracany.
f = T () => default; // ok
f = short x => 1; // syntax error at '=>'
f = ref int (ref int x) => ref x; // ok
f = static void (_) => { }; // ok
f = async async (async async) => async; // ok?
Analizator przyjrzy się temu, aby odróżnić wywołanie metody T() od wyrażenia lambda T () => e.
Jawne typy zwracane nie są obsługiwane w przypadku metod anonimowych zadeklarowanych przy użyciu składni delegate { }.
f = delegate int { return 1; }; // syntax error
f = delegate int (int x) { return x; }; // syntax error
Wnioskowanie typu metody powinno spowodować dokładne wnioskowanie z jawnego typu zwracanego lambda.
static void F<T>(Func<T, T> f) { ... }
F(int (i) => i); // Func<int, int>
Konwersje wariancji nie są dozwolone z typu zwracanego przez funkcję lambda do typu zwracanego przez delegata (co jest zgodne z podobnym zachowaniem dla typów parametrów).
Func<object> f1 = string () => null; // error
Func<object?> f2 = object () => x; // warning
Analizator umożliwia użycie wyrażeń lambda z typami zwracanymi ref w wyrażeniach bez dodatkowych nawiasów.
d = ref int () => x; // d = (ref int () => x)
F(ref int () => x); // F((ref int () => x))
var nie może być używane jako jawny typ zwrotny dla wyrażeń lambda.
class var { }
d = var (var v) => v; // error: contextual keyword 'var' cannot be used as explicit lambda return type
d = @var (var v) => v; // ok
d = ref var (ref var v) => ref v; // error: contextual keyword 'var' cannot be used as explicit lambda return type
d = ref @var (ref var v) => ref v; // ok
Typ naturalny (funkcja)
Wyrażenie funkcji anonimowej
Grupa metod ma typ naturalny, o ile wszystkie metody kandydujące w tej grupie mają wspólny podpis. (Jeśli grupa metod może zawierać metody rozszerzenia, kandydaci obejmują typ zawierający i wszystkie zakresy metody rozszerzenia).
Naturalnym typem anonimowego wyrażenia funkcji lub grupy metod jest function_type. function_type reprezentuje sygnaturę metody: typy parametrów i rodzaje ref oraz typ zwracany i rodzaj ref. Anonimowe wyrażenia funkcji lub grupy metod z tą samą sygnaturą mają te same function_type.
Function_types są używane tylko w kilku określonych kontekstach:
- niejawne i jawne konwersje
- wnioskowanie typu dla metod (§12.6.3) i najlepszy wspólny typ (§12.6.3.15)
- inicjatory
var
Wyłącznie w czasie kompilacji istnieje function_type: function_types nie pojawiają się w kodzie źródłowym ani w metadanych.
Konwersje
Z function_typeF wynikają niejawne konwersje function_type:
- Do function_type
G, jeśli parametry i zwracane typyFsą konwertowalne względem wariancji do parametrów i zwracanych typówG - Aby
System.MulticastDelegatealbo klasy bazowe oraz interfejsySystem.MulticastDelegate - Aby
System.Linq.Expressions.ExpressionlubSystem.Linq.Expressions.LambdaExpression
Anonimowe wyrażenia funkcji i grupy metod mają już konwersje z wyrażenia do delegowania typów i typów drzewa wyrażeń (zobacz anonimowe konwersje funkcji §10.7 i konwersje grup metod §10.8). Te konwersje są wystarczające do konwersji na silnie typizowane typy delegatów i typy drzewa wyrażeń. Powyższe konwersje typu funkcji dodają konwersje z typu wyłącznie do typów podstawowych: System.MulticastDelegate, System.Linq.Expressions.Expression, itp.
Nie ma konwersji na function_type z typu innego niż function_type. Nie ma jawnych konwersji dla function_types, ponieważ nie można odwoływać się do function_types w źródle.
Konwersja na System.MulticastDelegate, typ podstawowy lub interfejs, realizuje anonimową funkcję lub grupę metod jako instancję odpowiedniego typu delegata.
Konwersja na typ System.Linq.Expressions.Expression<TDelegate> lub typ bazowy realizuje wyrażenie lambda jako drzewo wyrażeń z odpowiednim typem delegata.
Delegate d = delegate (object obj) { }; // Action<object>
Expression e = () => ""; // Expression<Func<string>>
object o = "".Clone; // Func<object>
Function_type konwersje nie są konwersjami standardowymi, ani jawnymi, ani niejawnymi §10.4 i nie są brane pod uwagę podczas określania tego, czy operator konwersji zdefiniowanej przez użytkownika ma aplikacje do anonimowej funkcji lub grupy metod. Na podstawie oceny konwersji zdefiniowanych przez użytkownika §10.5.3:
Aby operator konwersji mógł mieć zastosowanie, musi być możliwe przeprowadzenie konwersji standardowej (§10.4) z typu źródłowego do typu operand operatora i musi być możliwe przeprowadzenie standardowej konwersji z typu wyniku operatora na typ docelowy.
class C
{
public static implicit operator C(Delegate d) { ... }
}
C c;
c = () => 1; // error: cannot convert lambda expression to type 'C'
c = (C)(() => 2); // error: cannot convert lambda expression to type 'C'
Ostrzeżenie jest zgłaszane dla niejawnej konwersji grupy metod na object, ponieważ konwersja jest prawidłowa, ale być może niezamierzona.
Random r = new Random();
object obj;
obj = r.NextDouble; // warning: Converting method group to 'object'. Did you intend to invoke the method?
obj = (object)r.NextDouble; // ok
Wnioskowanie typów
Istniejące reguły wnioskowania typu są w większości niezmienione (zobacz §12.6.3). Istnieje jednak kilka zmian poniżej do określonych faz wnioskowania typu.
Pierwsza faza
Pierwsza faza (§12.6.3.2) zezwala funkcji anonimowej na powiązanie się z Ti, nawet jeśli Ti nie jest typem delegata lub drzewem wyrażeń (być może parametrem typu ograniczonym na przykład do System.Delegate).
Dla każdego argumentu metody
Ei:
- Jeśli
Eijest funkcją anonimową , aTijest typem delegata lub typem drzewa wyrażeń, jawne wnioskowanie typu parametru jest wykonywane zEidoTi, a jawne wnioskowanie typu zwracanego jest wykonywane zEidoTi.- W przeciwnym razie, jeśli
Eima typU, axijest parametrem wartości, wnioskowania niższego jest wykonywane zUdoTi.- W przeciwnym razie, jeśli
Eima typU, axijest parametremreflubout, dokładne wnioskowanie jest wykonywane zUdoTi.- W przeciwnym razie dla tego argumentu nie jest wykonywane żadne wnioskowanie.
jawne wnioskowanie typu zwracanego
jawne określenie typu zwrotnego jest wykonywane na podstawie wyrażenia
Edo typuTw następujący sposób:
- Jeśli
Ejest funkcją anonimową z jawnym typem zwracanymUr, aTjest typem delegata lub typem drzewa wyrażeń z typem zwracanymVr, dokładne wnioskowanie (§12.6.3.9) jest wykonywane zUrdoVr.
Naprawa
Poprawianie (§12.6.3.12) zapewnia, że inne konwersje są preferowane nad konwersjami typu function_type. (Wyrażenia lambda i wyrażenia grupy metod przyczyniają się tylko do niższych granic, więc obsługa function_types jest wymagana tylko w przypadku niższych granic).
Niefiksowana zmienna typu
z zestawem granic jest stała w następujący sposób:
- Zestaw typów kandydatów
Ujpoczątkowo stanowi zestaw wszystkich typów w zestawie ograniczeń dlaXi, gdzie typy funkcji są pomijane w niższych granicach, jeśli istnieją jakiekolwiek typy, które nie są typami funkcji.- Następnie badamy każdą granicę dla
Xipo kolei: dla każdej dokładnej granicyUzXiwszystkie typyUj, które nie są identyczne zU, są usuwane z zestawu kandydatów. Dla każdej dolnej granicyUzXiwszystkie typyUj, do których nie istnieje niejawna konwersja zU, są usuwane z zestawu kandydatów. Dla każdej górnej granicyUXiwszystkich typówUj, z których nie ma niejawnej konwersji naUsą usuwane z zestawu kandydatów.- Jeśli wśród pozostałych typów kandydatów
Ujistnieje wyjątkowy typV, dla którego istnieje niejawna konwersja na wszystkie inne typy kandydatów, toXijest ustalony naV.- W przeciwnym razie wnioskowanie typu kończy się niepowodzeniem.
Najlepszy typ powszechny
Najlepszy typ typowy (§12.6.3.15) jest definiowany pod względem wnioskowania typu, więc powyższe zmiany wnioskowania typu mają zastosowanie również do najlepszego typu.
var fs = new[] { (string s) => s.Length, (string s) => int.Parse(s) }; // Func<string, int>[]
var
Funkcje anonimowe i grupy metod z typami funkcji mogą być używane jako inicjatory w deklaracjach var.
var f1 = () => default; // error: cannot infer type
var f2 = x => x; // error: cannot infer type
var f3 = () => 1; // System.Func<int>
var f4 = string () => null; // System.Func<string>
var f5 = delegate (object o) { }; // System.Action<object>
static void F1() { }
static void F1<T>(this T t) { }
static void F2(this string s) { }
var f6 = F1; // error: multiple methods
var f7 = "".F1; // error: the delegate type could not be inferred
var f8 = F2; // System.Action<string>
Typy funkcji nie są używane w przypisaniach do wywołań typu discard.
d = () => 0; // ok
_ = () => 1; // error
Typy delegatów
Typ delegata dla anonimowej funkcji lub grupy metod z parametrami typu P1, ..., Pn i typem zwrotu R to:
- Jeśli jakikolwiek parametr lub wartość zwracana nie jest przekazywana przez wartość, lub istnieje więcej niż 16 parametrów, lub którykolwiek z typów parametrów lub wartość zwracana są nieprawidłowymi argumentami typu (np.
(int* p) => { }), delegat jest syntetyzowany jakointernalanonimowy typ delegata z sygnaturą pasującą do anonimowej funkcji lub grupy metod oraz z nazwami parametrówarg1, ..., argnlubarg, jeśli jest to pojedynczy parametr. - jeśli
Rjestvoid, typ delegata jestSystem.Action<P1, ..., Pn>; - W przeciwnym wypadku typ delegata to
System.Func<P1, ..., Pn, R>.
Kompilator może w przyszłości zezwolić na powiązanie większej liczby podpisów z typami System.Action<> i System.Func<> (na przykład jeśli typy ref struct są dozwolone jako argumenty typu).
modopt() lub modreq() w sygnaturze grupy metod są ignorowane w odpowiednim typie delegata.
Jeśli dwie anonimowe funkcje lub grupy metod w tej samej kompilacji wymagają syntetyzowanych typów delegatów z tymi samymi typami parametrów i modyfikatorami, kompilator użyje tego samego syntetyzowanego typu delegata.
Rozpoznawanie przeciążenia
Element członkowski funkcji Better (§12.6.4.3) jest aktualizowany w celu preferowania elementów członkowskich, w których żadna z konwersji i żaden z argumentów typu nie zawiera wywnioskowanych typów z wyrażeń lambda lub grup metod.
Lepszy członek funkcji
... Biorąc pod uwagę listę argumentów
z zestawem wyrażeń argumentów i dwoma odpowiednimi elementami członkowskimi funkcji i z typami parametrów i , jest definiowana jako funkcji niż lepiej , jeśli
- dla każdego argumentu niejawna konwersja z
ExnaPxnie jest konwersją typów funkcjii
Mpjest metodą niegeneryjną lubMpjest metodą ogólną z parametrami typu{X1, X2, ..., Xp}i dla każdego parametru typuXiargument typu jest wnioskowany z wyrażenia lub typu innego niż function_typei- dla co najmniej jednego argumentu niejawna konwersja z
ExnaQxjest konwersją_typu_funkcji, lubMqjest metodą ogólną z parametrami typu{Y1, Y2, ..., Yq}, i dla co najmniej jednego parametru typuYiargument typu jest wnioskowany z typu_funkcji, lub- dla każdego argumentu niejawna konwersja z
ExnaQxnie jest lepsza niż niejawna konwersja zExnaPxi dla co najmniej jednego argumentu konwersja zExnaPxjest lepsza niż konwersja zExnaQx.
Lepsza konwersja z wyrażenia (§12.6.4.5) jest aktualizowana w celu preferowania konwersji, które nie obejmowały wywnioskowanych typów z wyrażeń lambda lub grup metod.
Ulepszona konwersja wyrażeń
Biorąc pod uwagę niejawne
C1konwersje, które konwertują z wyrażeniaEna typT1, oraz niejawneC2konwersje, które konwertują z wyrażeniaEna typT2,C1jest lepszą konwersją niżC2, jeśli:
C1nie jest function_type_conversion iC2jest function_type_conversionlubEjest niestały interpolated_string_expression,C1jest implicit_string_handler_conversion,T1jest applicable_interpolated_string_handler_type, orazC2nie jest implicit_string_handler_conversion.Enie jest dokładnie zgodny zT2i co najmniej jeden z następujących warunków jest spełniony:
Składnia
lambda_expression
: modifier* identifier '=>' (block | expression)
| attribute_list* modifier* type? lambda_parameters '=>' (block | expression)
;
lambda_parameters
: lambda_parameter
| '(' (lambda_parameter (',' lambda_parameter)*)? ')'
;
lambda_parameter
: identifier
| attribute_list* modifier* type? identifier equals_value_clause?
;
Otwarte problemy
Czy wartości domyślne powinny być obsługiwane dla parametrów wyrażenia lambda dla kompletności?
Czy System.Diagnostics.ConditionalAttribute powinno być niedozwolone w wyrażeniach lambda, skoro istnieje niewiele scenariuszy, gdzie można z nich skorzystać warunkowo?
([Conditional("DEBUG")] static (x, y) => Assert(x == y))(a, b); // ok?
Czy function_type powinien być dostępny za pośrednictwem interfejsu API kompilatora, oprócz wynikowego typu delegata?
Obecnie wnioskowany typ delegata używa System.Action<> lub System.Func<>, gdy typy parametrów i zwracane typy są prawidłowymi argumentami typów i, nie ma więcej niż 16 parametrów, a jeśli oczekiwany typ Action<> lub Func<> jest brakujący, zgłaszany jest błąd. Zamiast tego kompilator powinien używać System.Action<> lub System.Func<> niezależnie od arity? A jeśli brakuje oczekiwanego typu, zsyntetyzuje typ delegata w przeciwnym razie?
C# feature specifications