从数组中获取通用枚举器

Posted

技术标签:

【中文标题】从数组中获取通用枚举器【英文标题】:obtain generic enumerator from an array 【发布时间】:2009-08-13 15:21:24 【问题描述】:

在 C# 中,如何从给定数组中获取泛型枚举数?

在下面的代码中,MyArrayMyType 对象的数组。我想以所示的方式获得MyIEnumerator, 但似乎我得到了一个空的枚举器(虽然我已经确认MyArray.Length > 0)。

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

【问题讨论】:

只是出于好奇,为什么要获取枚举器? @Backwards_Dave 在我的情况下,在单线程环境中,有一个文件列表,每个文件都必须以异步方式处理一次。我可以使用索引来增加,但枚举更酷:) 【参考方案1】:

适用于 2.0+:

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

适用于 3.5+(花哨的 LINQy,效率稍低):

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

【讨论】:

LINQy 代码实际上返回的是 Cast 方法结果的枚举器,而不是数组的枚举器... Guffa:由于枚举器只提供只读访问权限,因此在使用方面并没有太大区别。 正如@Mehrdad 所暗示的,使用LINQ/Cast 具有完全不同的运行时行为,因为数组的每个元素 将通过一组额外的MoveNextCurrent 枚举器往返,如果数组很大,这可能会影响性能。无论如何,通过首先获得适当的枚举器(即使用我的答案中显示的两种方法之一),可以完全轻松地避免该问题。 第一个是更好的选择!不要让自己被完全多余的“花式LINQy”版本欺骗。 @GlennSlayden 不仅对于大型数组来说很慢,对于任何大小的数组来说都很慢。因此,如果您在最里面的循环中使用 myArray.Cast&lt;MyType&gt;().GetEnumerator(),即使对于很小的数组,它也可能会显着减慢您的速度。【参考方案2】:

您可以自己决定强制转换是否丑到足以保证一个无关的库调用:

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()

为了完整起见,还应注意以下内容是不正确的——并且会在运行时崩溃——因为T[] 选择 non-generic 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&lt;T&gt; 不从SZArrayEnumerator 继承——一个当前标记为“密封”的内部类——因为这将允许默认返回(协变)泛型枚举数?

【讨论】:

您在((IEnumerable&lt;int&gt;)arr) 中使用了额外的括号,但在(IEnumerator&lt;int&gt;)arr 中只使用了一组括号,这是有原因的吗? @Backwards_Dave 是的,这就是顶部正确示例和底部不正确示例之间的区别。检查 C# 一元“强制转换”运算符的operator precedence。 @GlennSlayden as 的 IL 会比强制转换更优化吗? "Casting" 与 as 将各自直接映射到它们自己的特定 IL 指令 -- castclassisinst 相比。尽管从定义上看,“as”直觉上似乎在做更多存在的“工作”,但这两者之间的实际性能差异可能在任何情况下都无法衡量。【参考方案3】:

由于我不喜欢选角,所以稍微更新一下:

your_array.AsEnumerable().GetEnumerator();

【讨论】:

AsEnumerable() 也进行强制转换,因此您仍在进行强制转换:) 也许您可以使用 your_array.OfType().GetEnumerator(); 不同之处在于它是在编译时完成的隐式转换,而不是在运行时完成的显式转换。因此,如果类型错误,您将遇到编译时错误而不是运行时错误。 @HankSchultz 如果类型错误,那么your_array.AsEnumerable() 将不会首先编译,因为AsEnumerable() 只能用于实现IEnumerable 的类型的实例。【参考方案4】:

为了让它尽可能干净,我喜欢让编译器完成所有工作。没有强制转换(因此它实际上是类型安全的)。不使用第三方库 (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 不,他们不是。运行时的工作因为我们知道Foo[] 实现了IEnumerable&lt;Foo&gt;,但是如果它发生了变化,它将不会在编译时被检测到。显式转换永远不是编译时证明。相反,分配/返回数组作为 IEnumerable 使用隐式转换,这是编译时证明。 @Toxantron 显式转换为 IEnumerable 不会编译,除非您尝试转换的类型实现 IEnumerable。当您说“但如果情况发生变化”时,您能举例说明您的意思吗? 当然可以编译。这就是InvalidCastException 的用途。您的编译器可能会警告您,某个强制转换不起作用。试试var foo = (int)new object()。它编译得很好并在运行时崩溃。【参考方案5】:

YourArray.OfType().GetEnumerator();

性能可能会好一点,因为它只需要检查类型,而不是强制转换。

【讨论】:

你必须在使用OfType&lt;..type..&gt;()时明确指定类型——至少在我的double[][]的情况下【参考方案6】:
    MyType[] arr =  new MyType(), new MyType(), new MyType() ;

    IEnumerable<MyType> enumerable = arr;

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

    foreach (MyType item in enumerable)
    

    

【讨论】:

你能解释一下上面的代码会执行什么吗> 这应该投票得更高!使用编译器的隐式转换是最干净和最简单的解决方案。【参考方案7】:

当然,您可以做的只是为数组实现自己的通用枚举器。

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 的 .NET 实现。您当然应该只这样做,这是值得付出努力的情况。大多数情况下并非如此。

【讨论】:

以上是关于从数组中获取通用枚举器的主要内容,如果未能解决你的问题,请参考以下文章

Kotlin,Spring Boot,JPA - 获取通用枚举的值 (E.valueOf(stringValue))

在休眠中读写通用枚举

通用可选枚举函数

如何从 EventLogEntryType 枚举中获取 int 值 [重复]

从不同核心数据实体获取数据并将结果转换为相应类的通用方法

Javascript:通用获取数组中的下一项