다음을 통해 공유


컬렉션 식 요소에 대한 더 나은 변환

메모

이 문서는 기능 사양입니다. 사양은 기능의 디자인 문서 역할을 합니다. 여기에는 기능 디자인 및 개발 중에 필요한 정보와 함께 제안된 사양 변경 내용이 포함됩니다. 이러한 문서는 제안된 사양 변경이 완료되고 현재 ECMA 사양에 통합될 때까지 게시됩니다.

기능 사양과 완료된 구현 간에 약간의 불일치가 있을 수 있습니다. 이러한 차이는 관련LDM(언어 디자인 모임) 노트에서 캡처됩니다.

사양문서에서 기능 명세를 C# 언어 표준으로 채택하는 프로세스에 대해 자세히 알아볼 수 있습니다.

챔피언 이슈: https://github.com/dotnet/csharplang/issues/8374

요약

params보다 일관되고 현재의 모호성 시나리오를 더 잘 처리하도록 더 나은 변환 규칙으로 업데이트합니다. 예를 들어 ReadOnlySpan<string>ReadOnlySpan<object> 현재 [""]대한 오버로드 확인 중에 모호성을 일으킬 수 있습니다.

상세 디자인

다음은 식 규칙에서 더 나은 변환입니다. 이것들은 https://github.com/dotnet/csharplang/blob/main/proposals/csharp-12.0/collection-expressions.md#overload-resolution규칙을 대체합니다.

이러한 규칙은 다음과 같습니다.

주어진 암시적 변환 C₁이(가) 식 E을(를) 형식 T₁으로(로) 변환하고, 암시적 변환 C₂이(가) 식 E을(를) 형식 T₂으로(로) 변환할 때, C₁은 다음 중 하나가 성립하는 경우, 보다 C₂입니다.

  • E 컬렉션 식이며 C₁더 나은 컬렉션 변환입니다C₂
  • E컬렉션 식이 아니며, 다음 중 하나가 성립합니다:
    • E 정확히 T₁ 일치하며 ET₂ 정확히 일치하지 않습니다.
    • ET₁T₂와 정확히 일치하거나 둘 모두와 일치하지 않으며, T₁더 나은 변환 대상T₂이다.
  • E 메서드 그룹입니다.

우리는 다음과 같이 표현식에서더 나은 컬렉션 변환을 위해 새로운 정의를 추가합니다.

주어진:

  • E[EL₁, EL₂, ..., ELₙ] 요소 식을 포함한 컬렉션 식입니다.
  • T₁T₂ 컬렉션 형식입니다.
  • E₁ T₁ 요소 형식입니다.
  • E₂ T₂ 요소 형식입니다.
  • CE₁ᵢELᵢ에서 E₁로의 변환 일련입니다.
  • CE₂ᵢELᵢ에서 E₂로의 변환 일련입니다.

E₁에서 E₂으로의 항등 변환이 있는 경우, 요소 변환은 서로 동등하게 좋습니다. 그렇지 않으면 E₁ 요소 변환이 다음과 같은 경우 E₂더 좋습니다.

  • ELᵢ에 대해 CE₁ᵢ은 적어도 CE₂ᵢ만큼 좋습니다.
  • CE₁ᵢ CE₂ᵢ보다 더 나은 하나 이상의 i가 있습니다. 그렇지 않으면 요소 변환 집합이 다른 집합보다 낫지 않으며 서로만큼 좋지도 않습니다.
    ELᵢ 스프레드 요소가 아닌 경우 식에서 더 나은 변환을 사용하여 변환 비교가 이루어집니다. 만약 ELᵢ 가 스프레드 요소라면, 우리는 스프레드 컬렉션의 요소 유형에서 E₁ 또는 E₂로 각각 더 나은 변환을 사용합니다.

C₁C₂더 나은 컬렉션 변환입니다.

  • T₁T₂ 모두 범위 형식이 아니며 T₁ 암시적으로 T₂변환할 수 있으며 T₂ 암시적으로 T₁변환할 수 없습니다.
  • E₁ E₂대한 ID 변환이 없으며 E₁ 요소 변환이 E₂더 낫습니다.
  • E₁E₂로의 항등 변환을 가지며, 다음 중 하나를 충족합니다.
    • T₁System.ReadOnlySpan<E₁>이고, T₂System.Span<E₂>또는
    • T₁System.ReadOnlySpan<E₁> 또는 System.Span<E₁>이며, T₂array_or_array_interface로, 요소 형식은 E₂입니다.

그렇지 않으면 컬렉션 형식이 더 낫지 않으며 결과가 모호합니다.

메모

이러한 규칙은 컬렉션 형식 간의 변환 없이 다른 요소 형식을 사용하는 오버로드를 노출하는 메서드가 빈 컬렉션 식에 대해 모호하다는 것을 의미합니다. 예를 들면 다음과 같습니다.

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

M([]); // Ambiguous

시나리오:

일반 영어에서는 컬렉션 형식 자체가 동일해야 합니다. 또는 명확하게 더 나은 (즉, List<T>List<T> 동일, List<T>IEnumerable<T>보다 명확하게 더 나은, List<T>HashSet<T> 비교할 수 없습니다), 더 나은 컬렉션 형식에 대 한 요소 변환도 동일 하거나 더 나은 (즉, ReadOnlySpan<object>대 한 Span<string>[""] 중에서 결정할 수 없습니다., 사용자가 해당 결정을 내려야 합니다). 이에 대한 더 많은 예는 다음과 같습니다.

T₁ T₂ E C₁ 변환 C₂ 변환 CE₁ᵢCE₂ᵢ 결과
List<int> List<byte> [1, 2, 3] [Identity, Identity, Identity] [Implicit Constant, Implicit Constant, Implicit Constant] CE₁ᵢ 더 좋습니다. List<int> 선택됨
List<int> List<byte> [(int)1, (byte)2] [Identity, Implicit Numeric] 해당 없음 T₂ 적용할 수 없음 List<int> 선택됨
List<int> List<byte> [1, (byte)2] [Identity, Implicit Numeric] [Implicit Constant, Identity] 둘 다 더 낫지 않다 모호한
List<int> List<byte> [(byte)1, (byte)2] [Implicit Numeric, Implicit Numeric] [Identity, Identity] CE₂ᵢ 더 좋습니다. List<byte> 선택됨
List<int?> List<long> [1, 2, 3] [Implicit Nullable, Implicit Nullable, Implicit Nullable] [Implicit Numeric, Implicit Numeric, Implicit Numeric] 둘 다 더 낫지 않다 모호한
List<int?> List<ulong> [1, 2, 3] [Implicit Nullable, Implicit Nullable, Implicit Nullable] [Implicit Numeric, Implicit Numeric, Implicit Numeric] CE₁ᵢ 더 좋습니다. List<int?> 선택됨
List<short> List<long> [1, 2, 3] [Implicit Numeric, Implicit Numeric, Implicit Numeric] [Implicit Numeric, Implicit Numeric, Implicit Numeric] CE₁ᵢ 더 좋습니다. List<short> 선택됨
IEnumerable<int> List<byte> [1, 2, 3] [Identity, Identity, Identity] [Implicit Constant, Implicit Constant, Implicit Constant] CE₁ᵢ 더 좋습니다. IEnumerable<int> 선택됨
IEnumerable<int> List<byte> [(byte)1, (byte)2] [Implicit Numeric, Implicit Numeric] [Identity, Identity] CE₂ᵢ 더 좋습니다. List<byte> 선택됨
int[] List<byte> [1, 2, 3] [Identity, Identity, Identity] [Implicit Constant, Implicit Constant, Implicit Constant] CE₁ᵢ 더 좋습니다. int[] 선택됨
ReadOnlySpan<string> ReadOnlySpan<object> ["", "", ""] [Identity, Identity, Identity] [Implicit Reference, Implicit Reference, Implicit Reference] CE₁ᵢ 더 좋습니다. ReadOnlySpan<string> 선택됨
ReadOnlySpan<string> ReadOnlySpan<object> ["", new object()] 해당 없음 [Implicit Reference, Identity] T₁ 적용할 수 없음 ReadOnlySpan<object> 선택됨
ReadOnlySpan<object> Span<string> ["", ""] [Implicit Reference] [Identity] CE₂ᵢ 더 좋습니다. Span<string> 선택됨
ReadOnlySpan<object> Span<string> [new object()] [Identity] 해당 없음 T₁ 적용할 수 없음 ReadOnlySpan<object> 선택됨
ReadOnlySpan<InterpolatedStringHandler> ReadOnlySpan<string> [$"{1}"] [Interpolated String Handler] [Identity] CE₁ᵢ 더 좋습니다. ReadOnlySpan<InterpolatedStringHandler> 선택됨
ReadOnlySpan<InterpolatedStringHandler> ReadOnlySpan<string> [$"{"blah"}"] [Interpolated String Handler] [Identity] - 그러나 상수 CE₂ᵢ 더 좋습니다. ReadOnlySpan<string> 선택됨
ReadOnlySpan<string> ReadOnlySpan<FormattableString> [$"{1}"] [Identity] [Interpolated String] CE₂ᵢ 더 좋습니다. ReadOnlySpan<string> 선택됨
ReadOnlySpan<string> ReadOnlySpan<FormattableString> [$"{1}", (FormattableString)null] 해당 없음 [Interpolated String, Identity] T₁ 적용할 수 없음 ReadOnlySpan<FormattableString> 선택됨
HashSet<short> Span<long> [1, 2] [Implicit Constant, Implicit Constant] [Implicit Numeric, Implicit Numeric] CE₁ᵢ 더 좋습니다. HashSet<short> 선택됨
HashSet<long> Span<short> [1, 2] [Implicit Numeric, Implicit Numeric] [Implicit Constant, Implicit Constant] CE₂ᵢ 더 좋습니다. Span<short> 선택됨

질문 열기

다른 형식보다 ReadOnlySpan/Span 얼마나 우선 순위를 지정해야 하나요?

현재 지정한 대로 다음 오버로드는 모호합니다.

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

여기까지 얼마나 가고 싶습니까? List<T> 변형은 합리적으로 보이며 List<T> 하위 형식은 많이 존재합니다. 그러나 HashSet 버전은 매우 다른 의미 체계를 가지고 있으며, 이 API에서 ReadOnlySpan 것보다 실제로 "더 나쁘다"고 확신합니까?