4

所以,我有一个里面有一个数组的类。目前,我枚举类项目的策略是使用代码foreach (item x in classInstance.InsideArray). 我更愿意使用foreach (item x in classInstance)并将数组设为私有。我主要担心的是我真的需要避免任何缓慢的事情。数组受到很多打击(并且有几百个项目)。枚举这个数组很便宜是至关重要的。一个想法是让类实现IEnumerable<item>,但InsideArray.getEnumerator()只给我一个非泛型枚举器。我也尝试实现IEnumerable接口。这很有效,但速度很慢,可能是因为拳击。

有没有办法让类本身可枚举而不会影响性能?

普通代码:

//Class
public class Foo {
    //Stuff
    public Item[,] InsideArray {get; private set;}
}

//Iteration.  Shows up all over the place
foreach (Item x in classInstance.InsideArray)
{
    //doStuff
}

调整后的慢得多的代码:

//Class
public class Foo : IEnumerable {
    //Stuff
    private Item[,] InsideArray;
    System.Collections.IEnumerator System.Collections.IEnumerable GetEnumerator()
    {
        return InsideArray.GetEnumerator();
    }
}

//Iteration.  Shows up all over the place
foreach (Item x in classInstance)
{
    //doStuff
}

注意:为非泛型迭代器添加一个实现是可能的,并且比我的慢速解决方案更快,但它仍然比直接使用数组差一些。我希望有一种方法可以以某种方式告诉 C#,“嘿,当我要求你迭代这个对象时,迭代它的数组,同样快,”但显然这不太可能......至少从建议的答案来看迄今。

4

4 回答 4

5

定制的迭代器可能会使其更快(编辑为返回已知类型):

Basic: 2468ms - -2049509440
Bespoke: 1087ms - -2049509440

(您可以将 ArrayIterator 直接用作 Foo 的 GetEnumerator - 本质上是从 ArrayEnumerator.GetEnumerator 复制代码;我的意思是表明类型化迭代器比接口更快)

带代码:

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

class Foo
{
    public struct ArrayIterator<T> : IEnumerator<T>
    {
        private int x, y;
        private readonly int width, height;
        private T[,] data;
        public ArrayIterator(T[,] data)
        {
            this.data = data;
            this.width = data.GetLength(0);
            this.height = data.GetLength(1);
            x = y = 0;
        }
        public void Dispose() { data = null; }
        public bool MoveNext()
        {
            if (++x >= width)
            {
                x = 0;
                y++;
            }
            return y < height;
        }
        public void Reset() { x = y = 0; }
        public T Current { get { return data[x, y]; } }
        object IEnumerator.Current { get { return data[x, y]; } }
    }
    public sealed class ArrayEnumerator<T> : IEnumerable<T>
    {
        private readonly T[,] arr;
        public ArrayEnumerator(T[,] arr) { this.arr = arr; }

        public ArrayIterator<T> GetEnumerator()
        {
            return new ArrayIterator<T>(arr);
        }

        System.Collections.Generic.IEnumerator<T> System.Collections.Generic.IEnumerable<T>.GetEnumerator()
        {
            return GetEnumerator();
        }
        System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator()
        {
            return GetEnumerator();
        }

    }
    public int[,] data;

    public IEnumerable<int> Basic()
    {
        foreach (int i in data) yield return i;
    }
    public ArrayEnumerator<int> Bespoke()
    {
        return new ArrayEnumerator<int>(data);
    }
    public Foo()
    {
        data = new int[500, 500];
        for (int x = 0; x < 500; x++)
            for (int y = 0; y < 500; y++)
            {
                data[x, y] = x + y;
            }
    }
    static void Main()
    {
        Test(1); // for JIT
        Test(500); // for real
        Console.ReadKey(); // pause
    }
    static void Test(int count)
    {
        Foo foo = new Foo();
        int chk;
        Stopwatch watch = Stopwatch.StartNew();
        chk = 0;
        for (int i = 0; i < count; i++)
        {
            foreach (int j in foo.Basic())
            {
                chk += j;
            }
        }
        watch.Stop();
        Console.WriteLine("Basic: " + watch.ElapsedMilliseconds + "ms - " + chk);

        watch = Stopwatch.StartNew();
        chk = 0;
        for (int i = 0; i < count; i++)
        {
            foreach (int j in foo.Bespoke())
            {
                chk += j;
            }
        }
        watch.Stop();
        Console.WriteLine("Bespoke: " + watch.ElapsedMilliseconds + "ms - " + chk);
    }
}
于 2009-05-17T09:41:48.917 回答
3

IEnumerable<item>在调用之前将您的数组转换为GetEnumerator(),您将获得通用的IEnumerator. 例如:

string[] names = { "Jon", "Marc" };
IEnumerator<string> enumerable = ((IEnumerable<string>)names).GetEnumerator();

它可能仍然比直接枚举数组要慢一些foreach(C# 编译器以不同的方式执行此操作),但至少您不会有任何其他问题。

编辑:

好的,您说您的其他尝试使用了索引器。您可以尝试这种方法,尽管我认为它不会更快:

public IEnumerable<Item> Items
{
    get
    {
        foreach (Item x in items)
        {
            yield return x;
        }
    }
}

另一种方法是尽量避免使用二维数组。这是绝对要求吗?创建单个数组后,您多久对其进行一次迭代?可能值得在创建时稍作打击以降低迭代成本。

编辑:另一个建议,这有点离题了......而不是将迭代器传递回调用者,为什么不让调用者说如何使用委托来处理每个项目?

public void ForEachItem(Action action)
{
    foreach (Item item in items)
    {
        action(item);
    }
}

缺点:

  • 您每次访问都会受到委托调用的惩罚。
  • 很难跳出循环(除了抛出异常)。有不同的方法可以解决这个问题,但是当我们遇到它时,让我们跨过那座桥。
  • 不熟悉委托的开发人员可能会感到有些困惑。
于 2009-05-17T07:30:39.293 回答
1

如何在类中添加索引器:

public MyInsideArrayType this[int index]
{
   get{return this.insideArray[index];
}

如果您真的需要 foreach 功能:

public IEnumerable<MyInsideArrayType> GetEnumerator()
{
   for(int i = 0; i<this.insideArray.Count;i++)
   {
      yield return this[i];
   }
}
于 2009-05-17T07:30:22.413 回答
-8

所有形式的迭代都很便宜。如果这个时代的任何人设法以某种方式编写和发布了一个昂贵的迭代器,他们将(正确地)被烧死。

过早的优化是邪恶的。

干杯。基思。

于 2009-05-17T07:33:01.037 回答