views:

750

answers:

2

Arrays are a fast way to iterate through an unordered set of items, and it's often nice for them to be read-only. While exposing arrays with the `readonly' keyword is useless because the contents of the array can still be altered, a ReadOnlyCollection<T> wrapper solves this. The problem is it's 4 times slower than a plain array in tests I've done. (I know, returning a copy of the array would only take a performance hit once, but ideally I wouldn't want to waste CPU time on that either.)

I have noticed I can get the benefit of a read-only sanity-check while preserving the performance of a plain array with a class like this:

class ReadOnlyArray<T>
{
    private readonly T[] array;

    public ReadOnlyArray(T[] a_array)
    {
        array = a_array;
    }

    // read-only because no `set'
    public T this[int i]
    { get { return array[i]; } }

    public int Length
    { get { return array.Length; } }
}

The problem is I lose the convenience of the foreach() syntax. I.e. I have to iterate through it with a for(;;) loop in order to keep the performance. -- I used to write C code, where every loop was a for(;;). Maybe I've become spoiled. -- If I implement IEnumerable<T> then I get the same performance as ReadOnlyCollection<T>, and this class is useless.

Any idea how to achieve the perfect combination of all 3 goals: a read-only sanity check, no performance loss, and the convenient foreach() syntax?

+3  A: 

I think the below does what you want. However, I think this is actually inadvisable. You're imposing an unnecessary and potentially confusing abstraction. Yes, the JIT will probably optimize it eventually, and your coworkers should catch on. But you're still doing something the language isn't meant to do.

EDIT: I've tweaked and better explained the below code, and mentioned a couple of options.

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

/*
  You can leave off the interface, or change to IEnumerable.  See below.
*/
class ReadOnlyArray<T> : IEnumerable<T>
{
    private readonly T[] array;

    public ReadOnlyArray(T[] a_array)
    {
        array = a_array;
    }

    // read-only because no `set'
    public T this[int i]
    { get { return array[i]; } }

    public int Length
    { get { return array.Length; } }

    /* 
       You can comment this method out if you don't implement IEnumerable<T>.
       Casting array.GetEnumerator to IEnumerator<T> will not work.
    */
    public IEnumerator<T> GetEnumerator()
    {
        foreach(T el in array)
        {
            yield return el;
        }
    }

    /* 
       If you don't implement any interface, change this to:
       public IEnumerator GetEnumerator()

       Or you can implement only IEnumerable (rather than IEnerable<T>)
       and keep "IEnumerator IEnumerable.GetEnumerator()"
    */
    IEnumerator IEnumerable.GetEnumerator()
    {
        return array.GetEnumerator();
    }
}
Matthew Flaschen
Nice. One question though, is the custom enumerator really needed?
Jonathan Allen
Dude, fix your code, it's marked up wrong.
Anthony Mastrean
Grauenwolf, you're right. You can do this, but it requires a cast to implement the generic version.
Matthew Flaschen
Why can't he just cast the array an IEnumerable?
Joel Coehoorn
Actually, that cast would fail at runtime.
Matthew Flaschen
Joel, in order for a type (in this case ReadOnlyArray) to be "foreach-ible", it must have a GetEnumerator method.
Matthew Flaschen
You can "foreach" an array, you can "foreach" an IEnumerable, and you _can_ cast an array directly to IEnumerable.
Joel Coehoorn
Yes, that's all correct. However, you can /not/ foreach a ReadOnlyArray (which I believe the OP wants to do), unless you have some glue code.
Matthew Flaschen
To clarify, "that cast would fail at runtime" was referring to casting array.GetEnumerator() to IEnumerator<T>. It had nothing to do with casting an array to an IEnumerable (you're right, that's possible).
Matthew Flaschen
My point is that building a whole new class for this is silly. Where ever it is that he already has this array and wants to expose readonly access to it, he can just return the array directly as an IEnumerable from a method or property whose type is "IEnumerable" or "IEnumerable<whatever type the array is>".
Joel Coehoorn
I agree it's silly/a bad idea. However, having a read-only access to the array (e.g. your SomeClass) still allows modifying elements.
Matthew Flaschen
A: 

Arrays are a fast way to iterate through an unordered set of items,

If that's all you need to do, just return the array as an IEnumerable. Don't implement IEnumerable yourself: the array already does that.

That should meet all three of your goals.

public class SomeClass
{ 
    private T[] myArray; // T could be any type

    public IEnumerable<T> MyReadOnlyArray { get { return myArray; } }
}
Joel Coehoorn
He wants to foreach through the ReadOnlyArray directly, not use a getter to access the array. However, I agree that this idea is not advisable for production code.
Matthew Flaschen
Perhaps the MyReadOnlyArray could be set as the default indexer. Then you could write foreach(var obj in someClassInstance)
Spence
@Spencer: this class isn't meant to "be" a readonly array. It's meant to be something else that happens to have the array he wants to return in a readonly way.
Joel Coehoorn
Yes, but he can modify elements like ((T[])mySomeClass.MyReadOnlyArray)[0] = "modified";
Matthew Flaschen