Sdílet prostřednictvím


Lepší převod z elementu výrazu kolekce

Poznámka

Tento článek je specifikace funkce. Specifikace slouží jako návrhový dokument pro funkci. Zahrnuje navrhované změny specifikace spolu s informacemi potřebnými při návrhu a vývoji funkce. Tyto články se publikují, dokud nebudou navrhované změny specifikace finalizovány a začleněny do aktuální specifikace ECMA.

Mezi specifikací funkce a dokončenou implementací může docházet k nějakým nesrovnalostem. Tyto rozdíly jsou zachyceny v poznámkách ze schůzky návrhu jazyka (LDM) .

Další informace o procesu přijetí specifikací funkcí do jazyka C# najdete v článku o specifikacích .

Problém šampiona: https://github.com/dotnet/csharplang/issues/8374

Shrnutí

Aktualizace lepších pravidel převodu tak, aby byly konzistentnější s paramsa lépe zvládly aktuální scénáře nejednoznačnosti. Například ReadOnlySpan<string> vs. ReadOnlySpan<object> může v současné době způsobit nejednoznačnost při rozlišení přetížení pro [""].

Podrobný návrh

Následuje účinnější převod podle pravidel výrazů. Nahradí pravidla v https://github.com/dotnet/csharplang/blob/main/proposals/csharp-12.0/collection-expressions.md#overload-resolution.

Tato pravidla jsou:

Vzhledem k implicitnímu převodu C₁, který převádí z výrazu E na typ T₁, a implicitnímu převodu C₂, který převádí z výrazu E na typ T₂, je C₁lepší převod než C₂, pokud platí jedna z následujících možností:

  • E je výraz pro kolekci a C₁ je lepší konverze kolekce z výrazu než C₂
  • E není výrazem kolekce a platí jeden z následujících:
    • E přesně odpovídá T₁ a E přesně neodpovídá T₂
    • E přesně odpovídá buď oběma, nebo ani jednomu z T₁ a T₂, a T₁ je lepším cílovým objektem konverze než T₂
  • E je skupina metod, ...

Přidáme novou definici pro lepší převod kolekce z výrazunásledujícím způsobem:

Daný:

  • E je výraz kolekce s elementárními výrazy [EL₁, EL₂, ..., ELₙ]
  • T₁ a T₂ jsou typy kolekcí.
  • E₁ je typ prvku T₁
  • E₂ je typ prvku T₂
  • CE₁ᵢ jsou série převodů z ELᵢ na E₁
  • CE₂ᵢ jsou série převodů z ELᵢ na E₂

Pokud existuje převod identity z E₁ na E₂, pak jsou převody prvků stejně dobré jako navzájem. Jinak jsou převody prvků na E₁lepší než převody prvků na E₂, pokud:

  • Pro každý ELᵢje CE₁ᵢ alespoň tak dobrý jako CE₂ᵢa
  • Existuje alespoň jedno i, kde je CE₁ᵢ lepší než CE₂ᵢ. Jinak ani jedna sada prvkových převodů není lepší než druhá, a nejsou ani stejně dobré.
    Porovnání převodů se provádí pomocí lepšího převodu z výrazu, pokud ELᵢ není rozprostřený prvek. Pokud je ELᵢ rozšiřující prvek, použijeme lepší konverzi z typu prvku rozšiřující kolekce na E₁ nebo E₂, v daném pořadí.

C₁ je lepší převod kolekce z výrazu než C₂, pokud:

  • T₁ i T₂ nejsou typy rozsahůa T₁ se implicitně konvertibilní na T₂a T₂ se implicitně nepřevést na T₁nebo
  • E₁ nemá identitní převod na E₂a převody prvků na E₁ jsou lepší než převody prvků na E₂nebo
  • E₁ má identitní převod na E₂a platí jedna z následujících podmínek:
    • T₁ je System.ReadOnlySpan<E₁>a T₂ je System.Span<E₂>nebo
    • T₁ je System.ReadOnlySpan<E₁> nebo System.Span<E₁>, a T₂ je rozhraní pole_nebo_pole s prvkem typu E₂

Jinak není žádný typ kolekce lepší a výsledek je nejednoznačný.

Poznámka

Tato pravidla znamenají, že metody, které poskytují přetížení přijímající různé typy prvků a postrádající konverzi mezi typy kolekcí, jsou nejednoznačné pro výrazy prázdných kolekcí. Příklad:

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

M([]); // Ambiguous

Scénáře:

V prosté angličtině musí být samotné typy kolekcí buď stejné, nebo jednoznačně lepší (tj. List<T> a List<T> jsou stejné, List<T> je jednoznačně lepší než IEnumerable<T>a List<T> a HashSet<T> nelze porovnat) a prvky pro lepší typ kolekce musí být také stejné nebo lepší (tj. nemůžeme rozhodnout mezi ReadOnlySpan<object> a Span<string> pro [""], uživatel musí učinit toto rozhodnutí). Tady jsou další příklady:

T₁ T₂ E C₁ převody C₂ převody CE₁ᵢ vs. CE₂ᵢ Výsledek
List<int> List<byte> [1, 2, 3] [Identity, Identity, Identity] [Implicit Constant, Implicit Constant, Implicit Constant] CE₁ᵢ je lepší List<int> je vybráno
List<int> List<byte> [(int)1, (byte)2] [Identity, Implicit Numeric] Nepoužitelné T₂ se nedá použít List<int> je vybráno
List<int> List<byte> [1, (byte)2] [Identity, Implicit Numeric] [Implicit Constant, Identity] Ani jeden z nich není lepší Mnohoznačný
List<int> List<byte> [(byte)1, (byte)2] [Implicit Numeric, Implicit Numeric] [Identity, Identity] CE₂ᵢ je lepší List<byte> je vybráno
List<int?> List<long> [1, 2, 3] [Implicit Nullable, Implicit Nullable, Implicit Nullable] [Implicit Numeric, Implicit Numeric, Implicit Numeric] Ani jeden z nich není lepší Mnohoznačný
List<int?> List<ulong> [1, 2, 3] [Implicit Nullable, Implicit Nullable, Implicit Nullable] [Implicit Numeric, Implicit Numeric, Implicit Numeric] CE₁ᵢ je lepší List<int?> je vybráno
List<short> List<long> [1, 2, 3] [Implicit Numeric, Implicit Numeric, Implicit Numeric] [Implicit Numeric, Implicit Numeric, Implicit Numeric] CE₁ᵢ je lepší List<short> je vybráno
IEnumerable<int> List<byte> [1, 2, 3] [Identity, Identity, Identity] [Implicit Constant, Implicit Constant, Implicit Constant] CE₁ᵢ je lepší IEnumerable<int> je vybráno
IEnumerable<int> List<byte> [(byte)1, (byte)2] [Implicit Numeric, Implicit Numeric] [Identity, Identity] CE₂ᵢ je lepší List<byte> je vybráno
int[] List<byte> [1, 2, 3] [Identity, Identity, Identity] [Implicit Constant, Implicit Constant, Implicit Constant] CE₁ᵢ je lepší int[] je vybráno
ReadOnlySpan<string> ReadOnlySpan<object> ["", "", ""] [Identity, Identity, Identity] [Implicit Reference, Implicit Reference, Implicit Reference] CE₁ᵢ je lepší ReadOnlySpan<string> je vybráno
ReadOnlySpan<string> ReadOnlySpan<object> ["", new object()] Nepoužitelné [Implicit Reference, Identity] T₁ se nedá použít ReadOnlySpan<object> je vybráno
ReadOnlySpan<object> Span<string> ["", ""] [Implicit Reference] [Identity] CE₂ᵢ je lepší Span<string> je vybráno
ReadOnlySpan<object> Span<string> [new object()] [Identity] Nepoužitelné T₁ se nedá použít ReadOnlySpan<object> je vybráno
ReadOnlySpan<InterpolatedStringHandler> ReadOnlySpan<string> [$"{1}"] [Interpolated String Handler] [Identity] CE₁ᵢ je lepší ReadOnlySpan<InterpolatedStringHandler> je vybráno
ReadOnlySpan<InterpolatedStringHandler> ReadOnlySpan<string> [$"{"blah"}"] [Interpolated String Handler] [Identity] – ale konstanta CE₂ᵢ je lepší ReadOnlySpan<string> je vybráno
ReadOnlySpan<string> ReadOnlySpan<FormattableString> [$"{1}"] [Identity] [Interpolated String] CE₂ᵢ je lepší ReadOnlySpan<string> je vybráno
ReadOnlySpan<string> ReadOnlySpan<FormattableString> [$"{1}", (FormattableString)null] Nepoužitelné [Interpolated String, Identity] T₁ se nedá použít ReadOnlySpan<FormattableString> je vybráno
HashSet<short> Span<long> [1, 2] [Implicit Constant, Implicit Constant] [Implicit Numeric, Implicit Numeric] CE₁ᵢ je lepší HashSet<short> je vybráno
HashSet<long> Span<short> [1, 2] [Implicit Numeric, Implicit Numeric] [Implicit Constant, Implicit Constant] CE₂ᵢ je lepší Span<short> je vybráno

Otevřené otázky

Jak daleko bychom měli upřednostnit ReadOnlySpan/Span oproti jiným typům?

Jak je uvedeno dnes, následující přetížení by bylo nejednoznačné:

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 chceme jít sem? Varianta List<T> se zdá rozumná a podtypů List<T> existuje spousta. Ale verze HashSet má velmi odlišnou sémantiku, jak jsme si jistí, že je skutečně horší než ReadOnlySpan v tomto rozhraní API?