배열에서 일반 열거자를 얻습니다.


91

C #에서 주어진 배열에서 제네릭 열거자를 어떻게 얻습니까?

아래 코드에서는 객체 MyArray의 배열입니다 MyType. MyIEnumerator표시된 패션 으로 얻고 싶지만 빈 열거자를 얻는 것 같습니다 (확인했지만 MyArray.Length > 0).

MyType[] MyArray = ... ;
IEnumerator<MyType> MyIEnumerator = MyArray.GetEnumerator() as IEnumerator<MyType>;

호기심 때문에 왜 열거자를 얻고 싶습니까?
David Klempfner

1
@Backwards_Dave의 경우 단일 스레드 환경에서 각각 비동기 방식으로 한 번 처리해야하는 파일 목록이 있습니다. :) 나는 높이기 위해 인덱스를 사용할 수 있지만 enumerables은 시원
hpaknia

답변:


103

2.0 이상에서 작동 :

((IEnumerable<MyType>)myArray).GetEnumerator()

3.5 이상에서 작동합니다 (멋진 LINQy, 약간 덜 효율적) :

myArray.Cast<MyType>().GetEnumerator()   // returns IEnumerator<MyType>

3
LINQy 코드는 실제로 배열에 대한 열거 자 대신 Cast 메서드의 결과에 대한 열거자를 반환합니다 ...
Guffa

1
Guffa : 열거자는 읽기 전용 액세스 만 제공하므로 사용 측면에서 큰 차이는 아닙니다.
Mehrdad Afshari

8
@Mehrdad가 암시 하듯이 Using LINQ/Cast배열의 모든 요소가 추가 MoveNextCurrent열거 자 왕복 집합을 통해 전달 되기 때문에 런타임 동작이 상당히 다르며 배열이 큰 경우 성능에 영향을 미칠 수 있습니다. 어쨌든 문제는 처음부터 적절한 열거자를 얻음으로써 완전하고 쉽게 피할 수 있습니다 (즉, 내 대답에 표시된 두 가지 방법 중 하나를 사용하여).
Glenn Slayden

7
첫 번째는 더 나은 옵션입니다! 아무도 완전히 불필요한 "멋진 LINQy"버전에 속지 마십시오.
henon

@GlennSlayden 거대한 배열의 경우 느릴뿐만 아니라 모든 크기의 배열에 대해 느립니다. 따라서 myArray.Cast<MyType>().GetEnumerator()가장 안쪽의 루프에서 사용 하면 작은 배열의 경우에도 상당히 느려질 수 있습니다.
Evgeniy Berezovsky

54

캐스팅이 외부 라이브러리 호출을 보증하기에 충분히 추악한 지 스스로 결정할 수 있습니다.

int[] arr;
IEnumerator<int> Get1()
{
    return ((IEnumerable<int>)arr).GetEnumerator();  // <-- 1 non-local call

    // ldarg.0 
    // ldfld int32[] foo::arr
    // castclass System.Collections.Generic.IEnumerable`1<int32>
    // callvirt instance class System.Collections.Generic.IEnumerator`1<!0> System.Collections.Generic.IEnumerable`1<int32>::GetEnumerator()
}

IEnumerator<int> Get2()
{
    return arr.AsEnumerable().GetEnumerator();   // <-- 2 non-local calls

    // ldarg.0 
    // ldfld int32[] foo::arr
    // call class System.Collections.Generic.IEnumerable`1<!!0> System.Linq.Enumerable::AsEnumerable<int32>(class System.Collections.Generic.IEnumerable`1<!!0>)
    // callvirt instance class System.Collections.Generic.IEnumerator`1<!0> System.Collections.Generic.IEnumerable`1<int32>::GetEnumerator()
}

그리고 완전성을 위해 다음은 정확하지 않으며 런타임에 충돌이 발생합니다. 왜냐하면 .NET 의 기본 (즉, 비명 시적) 구현에 대해 제네릭 T[]아닌IEnumerable 인터페이스를 선택 하기 때문 입니다 GetEnumerator().

IEnumerator<int> NoGet()                    // error - do not use
{
    return (IEnumerator<int>)arr.GetEnumerator();

    // ldarg.0 
    // ldfld int32[] foo::arr
    // callvirt instance class System.Collections.IEnumerator System.Array::GetEnumerator()
    // castclass System.Collections.Generic.IEnumerator`1<int32>
}

수수께끼는 왜 (공변) 제네릭 열거자가 기본적으로 반환되도록 허용하기 때문에 현재 '봉인'으로 표시된 내부 클래스 SZGenericArrayEnumerator<T>에서 상속 하지 않는 SZArrayEnumerator것입니다.


에서 여분의 괄호를 사용 ((IEnumerable<int>)arr)했지만 한 세트의 괄호 만 사용한 이유 가 (IEnumerator<int>)arr있습니까?
데이비드 Klempfner

1
@Backwards_Dave 예, 상단의 올바른 예제와 하단의 잘못된 예제의 차이를 만드는 이유입니다. C # 단항 "캐스트"연산자 의 연산자 우선 순위 를 확인합니다 .
Glenn Slayden

24

캐스팅을 좋아하지 않기 때문에 약간의 업데이트가 있습니다.

your_array.AsEnumerable().GetEnumerator();

AsEnumerable ()도 캐스팅을 수행하므로 여전히 캐스팅을 수행하고 있습니다. :) 아마도 your_array.OfType <T> (). GetEnumerator ();
Mladen B.

1
차이점은 런타임에 수행되는 명시 적 캐스트가 아니라 컴파일 시간에 수행되는 암시 적 캐스트라는 것입니다. 따라서 유형이 잘못된 경우 런타임 오류가 아닌 컴파일 시간 오류가 발생합니다.
Hank Schultz

@HankSchultz 유형이 잘못된 경우 .NET 을 구현하는 유형의 인스턴스에서만 사용할 수 your_array.AsEnumerable()있으므로 처음에는 컴파일되지 않습니다 . AsEnumerable()IEnumerable
데이비드 Klempfner

5

가능한 한 깔끔하게 만들기 위해 컴파일러가 모든 작업을 수행하도록하고 싶습니다. 캐스트가 없습니다 (실제로 형식에 안전함). 타사 라이브러리 (System.Linq)가 사용되지 않습니다 (런타임 오버 헤드 없음).

    public static IEnumerable<T> GetEnumerable<T>(this T[] arr)
    {
        return arr;
    }

// 코드를 사용하려면 :

    String[] arr = new String[0];
    arr.GetEnumerable().GetEnumerator()

이것은 모든 것을 깨끗하게 유지하는 컴파일러 마법을 활용합니다.

주목할 다른 점은 내 대답이 컴파일 타임 검사를 수행하는 유일한 대답이라는 것입니다.

다른 솔루션의 경우 "arr"유형이 변경되면 호출 코드가 컴파일되고 런타임에 실패하여 런타임 버그가 발생합니다.

내 대답으로 인해 코드가 컴파일되지 않으므로 잘못된 유형을 사용하고 있다는 신호를 보내므로 코드에 버그를 전달할 가능성이 적습니다.


GlennSlayden과 MehrdadAfshari가 보여준대로 캐스팅은 컴파일 타임 증명입니다.
t3chb0t

1
@ t3chb0t 아니에요. 을 구현 한다는 것을 알고 있기 때문에 런타임에 작업이 수행 되지만 변경되면 컴파일 타임에 감지되지 않습니다. 명시 적 캐스트는 컴파일 타임 증명이 아닙니다. 대신 배열을 IEnumerable <Foo>로 할당 / 반환하는 것은 컴파일 타임 증명 인 암시 적 캐스트를 사용합니다. Foo[]IEnumerable<Foo>
Toxantron

@Toxantron 캐스트하려는 형식이 IEnumerable을 구현하지 않는 한 IEnumerable <T>에 대한 명시 적 캐스트는 컴파일되지 않습니다. "하지만 변경 될 경우"라고 말할 때 의미의 예를 제공 할 수 있습니까?
David Klempfner

물론 컴파일됩니다. 이것이 InvalidCastException의 목적입니다. 컴파일러는 특정 캐스트가 작동하지 않는다고 경고 할 수 있습니다. 시도해보십시오 var foo = (int)new object(). 잘 컴파일되고 런타임에 충돌합니다.
Toxantron

2

YourArray.OfType (). GetEnumerator ();

캐스트가 아닌 유형 만 확인하면되므로 성능이 조금 더 향상 될 수 있습니다.


명시 적으로 사용하여 유형을 지정해야 OfType<..type..>()내 경우에는 적어도를 -double[][]
M. Mimpen

0
    MyType[] arr = { new MyType(), new MyType(), new MyType() };

    IEnumerable<MyType> enumerable = arr;

    IEnumerator<MyType> en = enumerable.GetEnumerator();

    foreach (MyType item in enumerable)
    {

    }

당신은> 수행 할 코드 위 설명해 주시겠습니까
Phani

이것은 훨씬 더 높은 투표 여야합니다! 컴파일러의 암시 적 캐스트를 사용하는 것이 가장 깨끗하고 쉬운 솔루션입니다.
Toxantron

-1

물론 여러분이 할 수있는 것은 배열에 대한 고유 한 일반 열거자를 구현하는 것입니다.

using System.Collections;
using System.Collections.Generic;

namespace SomeNamespace
{
    public class ArrayEnumerator<T> : IEnumerator<T>
    {
        public ArrayEnumerator(T[] arr)
        {
            collection = arr;
            length = arr.Length;
        }
        private readonly T[] collection;
        private int index = -1;
        private readonly int length;

        public T Current { get { return collection[index]; } }

        object IEnumerator.Current { get { return Current; } }

        public bool MoveNext() { index++; return index < length; }

        public void Reset() { index = -1; }

        public void Dispose() {/* Nothing to dispose. */}
    }
}

이것은 Glenn Slayden이 언급 한 SZGenericArrayEnumerator <T>의 .NET 구현과 다소 동일합니다. 물론 이것은 노력할 가치가있는 경우에만해야합니다. 대부분의 경우 그렇지 않습니다.

당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.