[c#] C #의 배열은 어떻게 부분적으로 IList <T>를 구현합니까?

아시다시피 C #의 배열은 IList<T>다른 인터페이스 중에서를 구현 합니다. 하지만 어떻게 든 Count 속성을 공개적으로 구현하지 않고이 작업을 수행합니다 IList<T>. 배열에는 Length 속성 만 있습니다.

이것은 인터페이스 구현에 대한 자체 규칙을 위반하는 C # /. NET의 노골적인 예입니까? 아니면 뭔가 빠졌습니까?



답변

Hans의 답변에 비추어 새로운 답변

Hans의 답변 덕분에 구현이 생각보다 다소 복잡하다는 것을 알 수 있습니다. 컴파일러와 CLR 모두 배열 유형이 구현하는 인상을주기 위해 매우 열심히 노력 IList<T>하지만 배열 분산으로 인해이 작업이 더 까다로워집니다. 특정 배열의 유형이 있기 때문에 한스의 대답과는 달리, 배열 유형 (어쨌든, 제로, 하나의 차원은), 직접 제네릭 컬렉션을 구현합니까 하지 않습니다 System.Array 단지의 그 – 염기 배열의 유형입니다. 배열 유형에 지원하는 인터페이스를 묻는 경우 일반 유형이 포함됩니다.

foreach (var type in typeof(int[]).GetInterfaces())
{
    Console.WriteLine(type);
}

산출:

System.ICloneable
System.Collections.IList
System.Collections.ICollection
System.Collections.IEnumerable
System.Collections.IStructuralComparable
System.Collections.IStructuralEquatable
System.Collections.Generic.IList`1[System.Int32]
System.Collections.Generic.ICollection`1[System.Int32]
System.Collections.Generic.IEnumerable`1[System.Int32]

1 차원, 0 기반 배열의 경우 언어 에 관한 한 배열도 실제로 구현 IList<T>됩니다. C # 사양의 섹션 12.1.2는 그렇게 말합니다. 따라서 기본 구현이 무엇을하든, 언어는 다른 인터페이스와 마찬가지로 구현 유형이 작동 해야합니다 . 이러한 관점에서 인터페이스 명시 적으로 구현되는 일부 멤버 (예 :)로 구현됩니다 . 그것이 무슨 일이 일어나고 있는지에 대한 언어 수준 에서 가장 좋은 설명 입니다.T[]IList<T>Count

이것은 1 차원 배열 (및 0부터 시작하는 배열에만 적용되며, 언어로서의 C #이 0이 아닌 배열에 대해 아무 말도하지 않음)에만 적용됩니다. 구현 T[,] 하지 않습니다IList<T> .

CLR 관점에서 보면 좀 더 펑키 한 일이 벌어지고 있습니다. 일반 인터페이스 유형에 대한 인터페이스 매핑을 가져올 수 없습니다. 예를 들면 :

typeof(int[]).GetInterfaceMap(typeof(ICollection<int>))

다음의 예외를 제공합니다.

Unhandled Exception: System.ArgumentException: Interface maps for generic
interfaces on arrays cannot be retrived.

그렇다면 왜 이상한가요? 글쎄요, 저는 그것이 실제로 배열 공분산 때문이라고 생각합니다. 이것은 타입 시스템 IMO에서 사마귀입니다. 비록 IList<T>입니다 하지 (안전하게 할 수 없다) 공변, 배열 공분산 작업이 할 수 있습니다 :

string[] strings = { "a", "b", "c" };
IList<object> objects = strings;

… 이것은 실제로 그렇지 않은 경우 구현 처럼 보입니다 .typeof(string[])IList<object>

CLI 사양 (ECMA-335) 파티션 1, 섹션 8.7.1에는 다음이 포함됩니다.

서명 유형 T는 서명 유형 U와 호환되며 다음 중 하나 이상이 유지되는 경우에만 가능합니다.

T는 0부터 1 등급 배열 V[]하고 U있다 IList<W>, 그리고 V는 W.와 배열 요소 호환-IS

(실제로 사양에서 버그라고 생각 ICollection<W>하거나 언급하지 않습니다 IEnumerable<W>.)

비 변이의 경우 CLI 사양은 언어 사양과 직접 함께 진행됩니다. 파티션 1의 8.9.1 섹션에서 :

또한 요소 유형이 T 인 생성 된 벡터는 인터페이스를 구현합니다 System.Collections.Generic.IList<U>. 여기서 U : = T입니다. (§8.7)

( 벡터 는 밑이 0 인 1 차원 배열입니다.)

지금의 관점에서 구현 세부 사항 , 명확하게 CLR 여기에 할당 호환성을 유지하기 위해 몇 가지 펑키 매핑을하고있다 : A는 때 string[]의 이행을 요구한다 ICollection<object>.Count, 그것은에 그것을 처리 할 수없는 매우 일반적인 방법. 이것이 명시 적 인터페이스 구현으로 간주됩니까? 인터페이스 매핑을 직접 요청하지 않는 한 언어 관점에서 항상 그렇게 행동 하기 때문에 그렇게 처리하는 것이 합리적이라고 생각합니다 .

어때 ICollection.Count?

지금까지 제네릭 인터페이스에 대해 이야기했지만 속성이 있는 비 제네릭 ICollectionCount있습니다. 이번에 는 인터페이스 매핑을 얻을 있으며 실제로 인터페이스는 System.Array. 의 ICollection.Count속성 구현에 대한 설명서 Array에는 명시 적 인터페이스 구현으로 구현되었다고 명시되어 있습니다.

이런 종류의 명시 적 인터페이스 구현이 “일반적인”명시 적 인터페이스 구현과 다른 방식을 누구나 생각할 수 있다면 더 자세히 살펴 보겠습니다.

명시 적 인터페이스 구현에 대한 이전 답변

배열에 대한 지식으로 인해 더 복잡한 위의 내용에도 불구하고 명시 적 인터페이스 구현을 통해 동일한 가시적 효과로 작업을 수행 할 수 있습니다 .

다음은 간단한 독립 실행 형 예제입니다.

public interface IFoo
{
    void M1();
    void M2();
}

public class Foo : IFoo
{
    // Explicit interface implementation
    void IFoo.M1() {}

    // Implicit interface implementation
    public void M2() {}
}

class Test
{
    static void Main()
    {
        Foo foo = new Foo();

        foo.M1(); // Compile-time failure
        foo.M2(); // Fine

        IFoo ifoo = foo;
        ifoo.M1(); // Fine
        ifoo.M2(); // Fine
    }
}


답변

아시다시피 C #의 배열은 IList<T>다른 인터페이스 중에서를 구현합니다 .

글쎄, 네, 음, 아니에요. 다음은 .NET 4 프레임 워크의 Array 클래스에 대한 선언입니다.

[Serializable, ComVisible(true)]
public abstract class Array : ICloneable, IList, ICollection, IEnumerable,
                              IStructuralComparable, IStructuralEquatable
{
    // etc..
}

System.Collections.Generic.IList <>가 아닌 System.Collections.IList를 구현 합니다. 할 수 없습니다. Array는 일반적이지 않습니다. 일반 IEnumerable <> 및 ICollection <> 인터페이스도 마찬가지입니다.

그러나 CLR은 구체적인 배열 유형을 즉석에서 생성하므로 기술적으로 이러한 인터페이스를 구현하는 배열 유형을 생성 할 수 있습니다. 그러나 이것은 사실이 아닙니다. 예를 들어 다음 코드를 시도하십시오.

using System;
using System.Collections.Generic;

class Program {
    static void Main(string[] args) {
        var goodmap = typeof(Derived).GetInterfaceMap(typeof(IEnumerable<int>));
        var badmap = typeof(int[]).GetInterfaceMap(typeof(IEnumerable<int>));  // Kaboom
    }
}
abstract class Base { }
class Derived : Base, IEnumerable<int> {
    public IEnumerator<int> GetEnumerator() { return null; }
    System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator() { return GetEnumerator(); }
}

“인터페이스를 찾을 수 없음”이있는 구체적인 배열 유형에 대해 GetInterfaceMap () 호출이 실패합니다. 그러나 IEnumerable <> 로의 캐스트는 문제없이 작동합니다.

이것은 꽥꽥 거리는 오리 같은 타이핑입니다. 모든 값 유형이 Object에서 파생 된 ValueType에서 파생된다는 착각을 일으키는 동일한 종류의 입력입니다. 컴파일러와 CLR은 모두 값 형식과 마찬가지로 배열 형식에 대한 특별한 지식을 가지고 있습니다. 컴파일러는 IList <>로 캐스팅하려는 사용자의 시도를보고 “좋아, 방법을 알고 있습니다!”라고 말합니다. 그리고 castclass IL 명령어를 내 보냅니다. CLR은 문제가 없으며 기본 배열 개체에서 작동하는 IList <> 구현을 제공하는 방법을 알고 있습니다. 실제로 이러한 인터페이스를 구현하는 래퍼 인 숨겨진 System.SZArrayHelper 클래스에 대한 지식이 내장되어 있습니다.

모든 사람의 주장과는 달리 명시 적으로 요청한 Count 속성은 다음과 같습니다.

    internal int get_Count<T>() {
        //! Warning: "this" is an array, not an SZArrayHelper. See comments above
        //! or you may introduce a security hole!
        T[] _this = JitHelpers.UnsafeCast<T[]>(this);
        return _this.Length;
    }

네, 당신은 확실히 그 코멘트를 “규칙을 깨는 것”이라고 부를 수 있습니다. 🙂 그렇지 않으면 그것은 매우 편리합니다. 그리고 매우 잘 숨겨져 있으므로 CLR 용 공유 소스 배포 인 SSCLI20에서이를 확인할 수 있습니다. 유형 대체가 발생하는 위치를 보려면 “IList”를 검색하십시오. 실제 동작을 볼 수있는 가장 좋은 곳은 clr / src / vm / array.cpp, GetActualImplementationForArrayGenericIListMethod () 메서드입니다.

CLR에서 이러한 종류의 대체는 WinRT (일명 Metro)에 대한 관리 코드를 작성할 수있는 CLR의 언어 프로젝션에서 발생하는 것과 비교할 때 매우 온화합니다. 거의 모든 핵심 .NET 유형이 여기에서 대체됩니다. IList <>는 예를 들어 완전히 관리되지 않는 형식 인 IVector <>에 매핑됩니다. 그 자체는 대체이며 COM은 제네릭 유형을 지원하지 않습니다.

글쎄, 그것은 커튼 뒤에서 일어나는 일을 보았습니다. 맵의 끝에 살고있는 용과 함께 매우 불편하고 이상하고 생소한 바다 일 수 있습니다. 지구를 평평하게 만들고 관리 코드에서 실제로 일어나는 일에 대한 다른 이미지를 모델링하는 것은 매우 유용 할 수 있습니다. 모두가 좋아하는 답변에 매핑하는 것은 그렇게 편안합니다. 값 유형에 대해 잘 작동하지 않지만 (구조체를 변경하지 마십시오!) 이것은 매우 잘 숨겨져 있습니다. GetInterfaceMap () 메서드 실패는 내가 생각할 수있는 추상화의 유일한 누수입니다.


답변

IList<T>.Count명시 적으로 구현됩니다 .

int[] intArray = new int[10];
IList<int> intArrayAsList = (IList<int>)intArray;
Debug.Assert(intArrayAsList.Count == 10);

이것은 간단한 배열 변수가있을 때 둘 다 사용할 수 Count없고 Length직접 사용할 수 없도록 수행 됩니다.

일반적으로 명시 적 인터페이스 구현은 유형의 모든 소비자가 유형에 대해 그렇게 생각하지 않고 특정 방식으로 유형을 사용할 수 있는지 확인하려는 경우에 사용됩니다.

편집 : 죄송합니다 . ICollection.Count명시 적으로 구현됩니다. 제네릭 IList<T>아래의 Hans descibes 로 처리됩니다 .


답변

명시 적 인터페이스 구현 . 요컨대, void IControl.Paint() { }또는 처럼 선언합니다 int IList<T>.Count { get { return 0; } }.


답변

IList의 명시 적 인터페이스 구현과 다르지 않습니다. 인터페이스를 구현한다고해서 해당 멤버가 클래스 멤버로 나타나야하는 것은 아닙니다. 그것은 하지 Count 속성을 구현, 그냥 X []에 노출되지 않습니다.


답변

참조 소스 사용 가능 :

//----------------------------------------------------------------------------------------
// ! READ THIS BEFORE YOU WORK ON THIS CLASS.
// 
// The methods on this class must be written VERY carefully to avoid introducing security holes.
// That's because they are invoked with special "this"! The "this" object
// for all of these methods are not SZArrayHelper objects. Rather, they are of type U[]
// where U[] is castable to T[]. No actual SZArrayHelper object is ever instantiated. Thus, you will
// see a lot of expressions that cast "this" "T[]". 
//
// This class is needed to allow an SZ array of type T[] to expose IList<T>,
// IList<T.BaseType>, etc., etc. all the way up to IList<Object>. When the following call is
// made:
//
//   ((IList<T>) (new U[n])).SomeIListMethod()
//
// the interface stub dispatcher treats this as a special case, loads up SZArrayHelper,
// finds the corresponding generic method (matched simply by method name), instantiates
// it for type <T> and executes it. 
//
// The "T" will reflect the interface used to invoke the method. The actual runtime "this" will be
// array that is castable to "T[]" (i.e. for primitivs and valuetypes, it will be exactly
// "T[]" - for orefs, it may be a "U[]" where U derives from T.)
//----------------------------------------------------------------------------------------
sealed class SZArrayHelper {
    // It is never legal to instantiate this class.
    private SZArrayHelper() {
        Contract.Assert(false, "Hey! How'd I get here?");
    }

    /* ... snip ... */
}

특히이 부분 :

인터페이스 스텁 디스패처 는이를 특수한 경우로 처리 하고 SZArrayHelper를로드 하고 해당하는 제네릭 메서드 (
메서드 이름으로 간단하게 일치)를 찾고 유형에 대해 인스턴스화하고 실행합니다.

(강조 광산)

소스 (위로 스크롤).


답변