Udostępnij za pomocą


Lepsza konwersja z elementu wyrażenia kolekcji

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 .

Problem z mistrzem: https://github.com/dotnet/csharplang/issues/8374

Streszczenie

Zaktualizowanie reguł konwersji, aby były bardziej spójne z paramsi lepiej radziły sobie z obecnymi scenariuszami niejednoznaczności. Na przykład ReadOnlySpan<string> a ReadOnlySpan<object> mogą obecnie powodować niejednoznaczności podczas rozwiązywania przeciążenia dla [""].

Szczegółowy projekt

Poniżej przedstawiono lepszą konwersję z reguł wyrażeń. Zastępują one reguły w https://github.com/dotnet/csharplang/blob/main/proposals/csharp-12.0/collection-expressions.md#overload-resolution.

Są to następujące reguły:

Biorąc pod uwagę niejawną konwersję C₁, która konwertuje z wyrażenia E na typ T₁, oraz niejawną konwersję C₂, która konwertuje z wyrażenia E na typ T₂, C₁ jest lepszą konwersją niż C₂, jeśli zachodzi jeden z następujących warunków:

  • E jest wyrażeniem kolekcji , a C₁ to lepsza konwersja wyrażenia kolekcji z niż C₂
  • E nie jest wyrażeniem kolekcji i jedno z następujących stwierdzeń jest prawdziwe:
    • E dokładnie pasuje do T₁ i E nie pasuje dokładnie do T₂
    • E dokładnie pasuje albo do żadnego z T₁ i T₂, albo do obu, a T₁ jest lepszym celem konwersji niż T₂
  • E jest grupą metod, ...

Dodamy nową definicję dla lepszej konwersji kolekcji z wyrażeniaw następujący sposób:

Podane:

  • E jest wyrażeniem kolekcji z wyrażeniami elementów [EL₁, EL₂, ..., ELₙ]
  • T₁ i T₂ to typy kolekcji
  • E₁ jest typem elementu T₁
  • E₂ jest typem elementu T₂
  • CE₁ᵢ to seria przekształceń od ELᵢ do E₁
  • CE₂ᵢ to seria przekształceń od ELᵢ do E₂

Jeśli istnieje konwersja tożsamości z E₁ na E₂, to konwersje elementów są tak samo dobre jak każda z osobna. W przeciwnym razie konwersje elementów na E₁lepsze niż konwersje elementów do E₂, jeśli:

  • Dla każdego ELᵢCE₁ᵢ jest przynajmniej tak dobry jak CE₂ᵢ, i
  • Istnieje co najmniej jedno i, dla którego CE₁ᵢ jest lepsze niż CE₂ᵢ, w przeciwnym razie żaden zestaw konwersji elementów nie jest lepszy od drugiego i nie są one równie dobre.
    Porównania konwersji są wykonywane, używając bardziej efektywnej konwersji z wyrażenia, jeśli ELᵢ nie jest elementem rozkładu. Jeśli ELᵢ jest elementem rozproszenia, stosujemy lepszą konwersję z typu elementu kolekcji rozproszenia do E₁ lub E₂, odpowiednio.

jest lepszą konwersją kolekcji wyrażenia z niż , jeśli:

  • Zarówno T₁, jak i T₂ nie są typów, a T₁ jest niejawnie konwertowany na T₂, a T₂ nie jest niejawnie konwertowany na T₁, lub
  • E₁ nie ma konwersji tożsamości do E₂, a konwersje elementów do E₁lepsze niż konwersje elementów do E₂lub
  • E₁ ma konwersję tożsamości na E₂, i spełniony jest jeden z następujących warunków:
    • T₁ jest System.ReadOnlySpan<E₁>, a T₂ jest System.Span<E₂>, lub
    • T₁ jest System.ReadOnlySpan<E₁> lub System.Span<E₁>, a T₂ jest array_or_array_interface z typem elementu E₂

W przeciwnym razie żaden typ kolekcji nie jest lepszy, a wynik jest niejednoznaczny.

Notatka

Te reguły oznaczają, że metody, które uwidaczniają przeciążenia, które przyjmują różne typy elementów i bez konwersji między typami kolekcji, są niejednoznaczne dla pustych wyrażeń kolekcji. Przykład:

public void M(ReadOnlySpan<int> ros) { ... }
public void M(Span<int?> span) { ... }

M([]); // Ambiguous

Scenariuszy:

W języku angielskim typy kolekcji muszą być takie same lub jednoznacznie lepsze (tj. List<T> i List<T> są takie same, List<T> są jednoznacznie lepsze niż IEnumerable<T>i List<T> i HashSet<T> nie można porównać), a konwersje elementów dla lepszego typu kolekcji również muszą być takie same lub lepsze (tj. nie możemy zdecydować między ReadOnlySpan<object> i Span<string> dla [""], użytkownik musi podjąć decyzję). Więcej przykładów:

T₁ T₂ E C₁ konwersje C₂ konwersje CE₁ᵢ a CE₂ᵢ Wynik
List<int> List<byte> [1, 2, 3] [Identity, Identity, Identity] [Implicit Constant, Implicit Constant, Implicit Constant] CE₁ᵢ jest lepszy List<int> zostaje wybrane
List<int> List<byte> [(int)1, (byte)2] [Identity, Implicit Numeric] Nie dotyczy T₂ nie ma zastosowania List<int> zostaje wybrane
List<int> List<byte> [1, (byte)2] [Identity, Implicit Numeric] [Implicit Constant, Identity] Żadna z nich nie jest lepsza Dwuznaczny
List<int> List<byte> [(byte)1, (byte)2] [Implicit Numeric, Implicit Numeric] [Identity, Identity] CE₂ᵢ jest lepszy List<byte> zostaje wybrane
List<int?> List<long> [1, 2, 3] [Implicit Nullable, Implicit Nullable, Implicit Nullable] [Implicit Numeric, Implicit Numeric, Implicit Numeric] Żadna z nich nie jest lepsza Dwuznaczny
List<int?> List<ulong> [1, 2, 3] [Implicit Nullable, Implicit Nullable, Implicit Nullable] [Implicit Numeric, Implicit Numeric, Implicit Numeric] CE₁ᵢ jest lepszy List<int?> zostaje wybrane
List<short> List<long> [1, 2, 3] [Implicit Numeric, Implicit Numeric, Implicit Numeric] [Implicit Numeric, Implicit Numeric, Implicit Numeric] CE₁ᵢ jest lepszy List<short> zostaje wybrane
IEnumerable<int> List<byte> [1, 2, 3] [Identity, Identity, Identity] [Implicit Constant, Implicit Constant, Implicit Constant] CE₁ᵢ jest lepszy IEnumerable<int> zostaje wybrane
IEnumerable<int> List<byte> [(byte)1, (byte)2] [Implicit Numeric, Implicit Numeric] [Identity, Identity] CE₂ᵢ jest lepszy List<byte> zostaje wybrane
int[] List<byte> [1, 2, 3] [Identity, Identity, Identity] [Implicit Constant, Implicit Constant, Implicit Constant] CE₁ᵢ jest lepszy int[] zostaje wybrane
ReadOnlySpan<string> ReadOnlySpan<object> ["", "", ""] [Identity, Identity, Identity] [Implicit Reference, Implicit Reference, Implicit Reference] CE₁ᵢ jest lepszy ReadOnlySpan<string> zostaje wybrane
ReadOnlySpan<string> ReadOnlySpan<object> ["", new object()] Nie dotyczy [Implicit Reference, Identity] T₁ nie ma zastosowania ReadOnlySpan<object> zostaje wybrane
ReadOnlySpan<object> Span<string> ["", ""] [Implicit Reference] [Identity] CE₂ᵢ jest lepszy Span<string> zostaje wybrane
ReadOnlySpan<object> Span<string> [new object()] [Identity] Nie dotyczy T₁ nie ma zastosowania ReadOnlySpan<object> zostaje wybrane
ReadOnlySpan<InterpolatedStringHandler> ReadOnlySpan<string> [$"{1}"] [Interpolated String Handler] [Identity] CE₁ᵢ jest lepszy ReadOnlySpan<InterpolatedStringHandler> zostaje wybrane
ReadOnlySpan<InterpolatedStringHandler> ReadOnlySpan<string> [$"{"blah"}"] [Interpolated String Handler] [Identity] — ale stała wartość CE₂ᵢ jest lepszy ReadOnlySpan<string> zostaje wybrane
ReadOnlySpan<string> ReadOnlySpan<FormattableString> [$"{1}"] [Identity] [Interpolated String] CE₂ᵢ jest lepszy ReadOnlySpan<string> zostaje wybrane
ReadOnlySpan<string> ReadOnlySpan<FormattableString> [$"{1}", (FormattableString)null] Nie dotyczy [Interpolated String, Identity] T₁ nie ma zastosowania ReadOnlySpan<FormattableString> zostaje wybrane
HashSet<short> Span<long> [1, 2] [Implicit Constant, Implicit Constant] [Implicit Numeric, Implicit Numeric] CE₁ᵢ jest lepszy HashSet<short> zostaje wybrane
HashSet<long> Span<short> [1, 2] [Implicit Numeric, Implicit Numeric] [Implicit Constant, Implicit Constant] CE₂ᵢ jest lepszy Span<short> zostaje wybrane

Otwórz pytania

Jak bardzo powinniśmy priorytetyzować ReadOnlySpan/Span nad innymi typami?

Jak określono dzisiaj, następujące przeciążenia byłyby niejednoznaczne:

C.M1(["Hello world"]); // Ambiguous, no tiebreak between ROS and List
C.M2(["Hello world"]); // Ambiguous, no tiebreak between Span and List

C.M3(["Hello world"]); // Ambiguous, no tiebreak between ROS and MyList.

C.M4(["Hello", "Hello"]); // Ambiguous, no tiebreak between ROS and HashSet. Created collections have different contents

class C
{
    public static void M1(ReadOnlySpan<string> ros) {}
    public static void M1(List<string> list) {}

    public static void M2(Span<string> ros) {}
    public static void M2(List<string> list) {}

    public static void M3(ReadOnlySpan<string> ros) {}
    public static void M3(MyList<string> list) {}

    public static void M4(ReadOnlySpan<string> ros) {}
    public static void M4(HashSet<string> hashset) {}
}

class MyList<T> : List<T> {}

Jak daleko chcemy tu przejść? Wariant List<T> wydaje się rozsądny, a podtypy List<T> istnieją aplenty. Ale czy wersja HashSet ma bardzo różną semantykę, jak bardzo jesteśmy pewni, że jest ona rzeczywiście "gorsza" niż ReadOnlySpan w tym interfejsie API?