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


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은 배열의 모든 요소가 추가 MoveNext및 Current열거 자 왕복 집합을 통해 전달 되기 때문에 런타임 동작이 상당히 다르며 배열이 큰 경우 성능에 영향을 미칠 수 있습니다. 어쨌든 문제는 처음부터 적절한 열거자를 얻음으로써 완전하고 쉽게 피할 수 있습니다 (즉, 내 대답에 표시된 두 가지 방법 중 하나를 사용하여).
— 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.