Megosztás a következőn keresztül:


Jobb átalakítás gyűjteménykifejezés-elemből

Jegyzet

Ez a cikk egy funkcióspecifikáció. A specifikáció a funkció tervezési dokumentumaként szolgál. Tartalmazza a specifikáció javasolt módosításait, valamint a funkció tervezése és fejlesztése során szükséges információkat. Ezeket a cikkeket mindaddig közzéteszik, amíg a javasolt specifikációmódosításokat nem véglegesítik, és be nem építik a jelenlegi ECMA-specifikációba.

A szolgáltatás specifikációja és a befejezett implementáció között eltérések lehetnek. Ezeket a különbségeket a vonatkozó nyelvi tervezési értekezlet (LDM) megjegyzései rögzítik.

A funkcióspektusok C# nyelvi szabványba való bevezetésének folyamatáról a specifikációkcímű cikkben olvashat bővebben.

Bajnoki probléma: https://github.com/dotnet/csharplang/issues/8374

Összefoglalás

A jobb konverziós szabályok frissítése annak érdekében, hogy konzisztensebben illeszkedjenek a params-hoz, és jobban kezeljék a jelenlegi kétértelműségi helyzeteket. A ReadOnlySpan<string> és a ReadOnlySpan<object> például jelenleg kétértelműséget okozhatnak a [""]túlterhelésének feloldásakor.

Részletes tervezés

Az alábbiakban a kifejezésszabályok jobb konvertálását mutatjuk be. Ezek váltják fel a szabályokat a https://github.com/dotnet/csharplang/blob/main/proposals/csharp-12.0/collection-expressions.md#overload-resolution-ban.

Ezek a szabályok a következők:

Egy kifejezésből C₁Etípussá konvertáló implicit konverziós T₁ és egy kifejezésből C₂ típussá Ekonvertáló implicit konverziós T₂C₁jobb konverziós, mint C₂, ha az alábbiak valamelyike fennáll:

  • E egy gyűjteménykifejezés, és C₁ egy jobb gyűjteményátalakítás a kifejezés alapján, mint C₂.
  • E nincs gyűjteménykifejezés, és az alábbiak egyike érvényes:
    • E pontosan egyezik T₁, és E nem egyezik meg pontosan T₂
    • E pontosan megegyezik, vagy mindkettővel, vagy egyikkel sem, T₁ és T₂közül, és T₁jobb konverziós célpont, mint T₂
  • E egy metóduscsoport, ...

Új definíciót adunk hozzá jobb gyűjteményátalakításhoz a kifejezésből, az alábbiak szerint:

Adott:

  • A E egy olyan gyűjteménykifejezés, amely [EL₁, EL₂, ..., ELₙ] elemkifejezéseket tartalmaz.
  • T₁ és T₂ gyűjteménytípusok
  • E₁ a T₁ elemtípusa
  • E₂ a T₂ elemtípusa
  • A(z) CE₁ᵢ a(z) ELᵢ-től a(z) E₁-ig tartó átalakulások sorozata.
  • A(z) CE₂ᵢ a(z) ELᵢ-től a(z) E₂-ig tartó átalakulások sorozata.

Ha identitásátalakítás történik E₁-ról E₂- ra, akkor az elemátalakítások ugyanolyan jók, mint egymás. Ellenkező esetben az elemek E₁ való konvertálása jobb, mintE₂, ha:

  • Minden ELᵢesetében CE₁ᵢ legalább olyan jó, mint CE₂ᵢ, és
  • Van legalább egy i, ahol CE₁ᵢ jobb, mint CE₂ᵢ Egyébként egyik elemátalakítás sem jobb, mint a másik, és ők sem olyan jók, mint egymás.
    A konverziós összehasonlítások akkor készülnek jobb konverzióval a kifejezésből, ha ELᵢ nem terjeszkedő elem. Ha a ELᵢ egy kibontott elem, akkor jobb átalakítást használunk a kibővített kollekció elemtípusáról E₁-re vagy E₂-re.

C₁ egy jobb gyűjteményátalakítás kifejezésből, mint C₂, ha:

  • A T₁ és a T₂ nem típusok, és T₁ implicit átalakítható T₂-re, míg T₂ nem implicit átalakítható T₁-re, vagy
  • E₁ nem rendelkezik identitásátalakítással E₂, és az E₁ elemátalakítások jobbak, mint az-ra történő E₂elemátalakítások, vagy
  • E₁-nak van egy identitáskonverziója E₂-re, és az alábbiak egyike igaz:
    • T₁ System.ReadOnlySpan<E₁>, T₂ pedig System.Span<E₂>, vagy
    • T₁ System.ReadOnlySpan<E₁> vagy System.Span<E₁>, és T₂ egy elemtípusú tömb_vagy_tömbfelületE₂.

Ellenkező esetben egyik gyűjteménytípus sem jobb, és az eredmény nem egyértelmű.

Jegyzet

Ezek a szabályok azt jelentik, hogy az üres gyűjteménykifejezések esetében nem egyértelműek azok a módszerek, amelyek különböző elemtípusokat használó túlterheléseket fednek fel, és a gyűjteménytípusok közötti átalakítás nélkül. Példaként:

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

M([]); // Ambiguous

Forgatókönyvek:

Egyszerű angol nyelven, maguknak a gyűjteménytípusoknak azonosnak vagy egyértelműen jobbnak kell lenniük (vagyis a List<T> és a List<T> azonosak, List<T> egyértelműen jobb, mint IEnumerable<T>, és List<T> és HashSet<T> nem lehet összehasonlítani), és a jobb gyűjteménytípus elemátalakításainak is azonosnak vagy jobbnak kell lenniük (azaz nem tudjuk eldönteni, hogy ReadOnlySpan<object> és Span<string> között [""], a felhasználónak meg kell hoznia ezt a döntést). További példák erre:

T₁ T₂ E C₁ Konverziók C₂ Konverziók CE₁ᵢ és CE₂ᵢ Eredmény
List<int> List<byte> [1, 2, 3] [Identity, Identity, Identity] [Implicit Constant, Implicit Constant, Implicit Constant] CE₁ᵢ a jobb választás List<int> van kiválasztva
List<int> List<byte> [(int)1, (byte)2] [Identity, Implicit Numeric] Nem alkalmazható T₂ nem alkalmazható List<int> van kiválasztva
List<int> List<byte> [1, (byte)2] [Identity, Implicit Numeric] [Implicit Constant, Identity] Egyik sem jobb Félreérthető
List<int> List<byte> [(byte)1, (byte)2] [Implicit Numeric, Implicit Numeric] [Identity, Identity] CE₂ᵢ a jobb választás List<byte> van kiválasztva
List<int?> List<long> [1, 2, 3] [Implicit Nullable, Implicit Nullable, Implicit Nullable] [Implicit Numeric, Implicit Numeric, Implicit Numeric] Egyik sem jobb Félreérthető
List<int?> List<ulong> [1, 2, 3] [Implicit Nullable, Implicit Nullable, Implicit Nullable] [Implicit Numeric, Implicit Numeric, Implicit Numeric] CE₁ᵢ a jobb választás List<int?> van kiválasztva
List<short> List<long> [1, 2, 3] [Implicit Numeric, Implicit Numeric, Implicit Numeric] [Implicit Numeric, Implicit Numeric, Implicit Numeric] CE₁ᵢ a jobb választás List<short> van kiválasztva
IEnumerable<int> List<byte> [1, 2, 3] [Identity, Identity, Identity] [Implicit Constant, Implicit Constant, Implicit Constant] CE₁ᵢ a jobb választás IEnumerable<int> van kiválasztva
IEnumerable<int> List<byte> [(byte)1, (byte)2] [Implicit Numeric, Implicit Numeric] [Identity, Identity] CE₂ᵢ a jobb választás List<byte> van kiválasztva
int[] List<byte> [1, 2, 3] [Identity, Identity, Identity] [Implicit Constant, Implicit Constant, Implicit Constant] CE₁ᵢ a jobb választás int[] van kiválasztva
ReadOnlySpan<string> ReadOnlySpan<object> ["", "", ""] [Identity, Identity, Identity] [Implicit Reference, Implicit Reference, Implicit Reference] CE₁ᵢ a jobb választás ReadOnlySpan<string> van kiválasztva
ReadOnlySpan<string> ReadOnlySpan<object> ["", new object()] Nem alkalmazható [Implicit Reference, Identity] T₁ nem alkalmazható ReadOnlySpan<object> van kiválasztva
ReadOnlySpan<object> Span<string> ["", ""] [Implicit Reference] [Identity] CE₂ᵢ a jobb választás Span<string> van kiválasztva
ReadOnlySpan<object> Span<string> [new object()] [Identity] Nem alkalmazható T₁ nem alkalmazható ReadOnlySpan<object> van kiválasztva
ReadOnlySpan<InterpolatedStringHandler> ReadOnlySpan<string> [$"{1}"] [Interpolated String Handler] [Identity] CE₁ᵢ a jobb választás ReadOnlySpan<InterpolatedStringHandler> van kiválasztva
ReadOnlySpan<InterpolatedStringHandler> ReadOnlySpan<string> [$"{"blah"}"] [Interpolated String Handler] [Identity] – De állandó CE₂ᵢ a jobb választás ReadOnlySpan<string> van kiválasztva
ReadOnlySpan<string> ReadOnlySpan<FormattableString> [$"{1}"] [Identity] [Interpolated String] CE₂ᵢ a jobb választás ReadOnlySpan<string> van kiválasztva
ReadOnlySpan<string> ReadOnlySpan<FormattableString> [$"{1}", (FormattableString)null] Nem alkalmazható [Interpolated String, Identity] T₁ nem alkalmazható ReadOnlySpan<FormattableString> van kiválasztva
HashSet<short> Span<long> [1, 2] [Implicit Constant, Implicit Constant] [Implicit Numeric, Implicit Numeric] CE₁ᵢ a jobb választás HashSet<short> van kiválasztva
HashSet<long> Span<short> [1, 2] [Implicit Numeric, Implicit Numeric] [Implicit Constant, Implicit Constant] CE₂ᵢ a jobb választás Span<short> van kiválasztva

Kérdések megnyitása

Milyen mértékben rangsoroljuk a ReadOnlySpan/Span más típusokkal szemben?

A ma megadottak szerint a következő túlterhelések nem egyértelműek:

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> {}

Meddig akarunk idemenni? A List<T> változat ésszerűnek tűnik, és a List<T> altípusai bőségesen léteznek. De a HashSet verzió nagyon különböző szemantikával rendelkezik, mennyire vagyunk biztosak abban, hogy valójában "rosszabb", mint ReadOnlySpan ebben az API-ban?