3
\$\begingroup\$

Between .NET's built-in collection libraries and LINQ extension methods, I never have to use arrays. An unfortunate consequence of this is that I am probably less competent in working with this very important Type than I should be. To help myself get a better grip on the Array class, I wrote some C# helper methods that mimic the "Mutator" methods in the JavaScript array type. These methods are documented thoroughly at https://developer.mozilla.org/en-US/docs/JavaScript/Reference/Global_Objects/Array . I omitted the methods that already have analogous implementations in .NET, (including reverse and sort). I also tried to avoid using LINQ, on which I have grown pretty dependent. (LINQ also manipulates arrays under the hood, so a properly written direct approach should perform better).

I appreciate : (1) any feedback and/or highlighting of anywhere I may have messed up, (2) highlighting of any errors that may not necessarily but could potentially cause problems (such as thread-safety issues); (3) any optimization techniques that I should have used; and, (4) anything I did that was generally stupid.

I use an IsNullOrEmpty extension method that is equivalent to ReferenceEquals(array,null) || array.Length == 0

public static class ArrayMutator
{
    public static T Pop<T>(ref T[] array)
    {
        Contract.Requires<ArgumentException>(!array.IsNullOrEmpty());
        T result = array[array.Length - 1];
        if (array.Length == 1)
        {
            array = new T[0];
        }
        else
        {
            var popped = new T[array.Length - 1];
            System.Array.Copy(array, 0, popped, 0, array.Length - 1);
            array = popped;
        }
        return result;
    }

    public static int Push<T>(ref T[] array, T element)
    {
        if (array.IsNullOrEmpty())
        {
            array = new T[1] {element};
            return 1;
        }
        var pushed = new T[array.Length + 1];
        array.CopyTo(pushed, 0);
        pushed[array.Length] = element;
        array = pushed;
        return pushed.Length;
    }

    public static int Push<T>(ref T[] array, params T[] elements)
    {
        if (array.IsNullOrEmpty())
        {
            if (elements.IsNullOrEmpty())
            {
                array = new T[0];
                return 0;
            }
            else
            {
                var elementsCopy = new T[elements.Length];
                elements.CopyTo(elementsCopy, 0);
                array = elementsCopy;
                return elementsCopy.Length;
            }
        }
        if (elements.IsNullOrEmpty())
        {
            return array.Length;
        }
        int total = array.Length + elements.Length;
        var pushed = new T[total];
        array.CopyTo(pushed, 0);
        elements.CopyTo(pushed, array.Length);
        array = pushed;
        return total;
    }

    public static T Shift<T>(ref T[] array)
    {
        Contract.Requires<ArgumentException>(!array.IsNullOrEmpty());
        T result = array[0];
        if (array.Length == 1)
        {
            array = new T[0];
        }
        else
        {
            var shifted = new T[array.Length - 1];
            System.Array.Copy(array, 1, shifted, 0, array.Length - 1);
            array = shifted;
        }
        return result;
    }

    public static T[] Splice<T>(ref T[] array, int index, int removeCount, params T[] elements)
    {
        Contract.Requires<ArgumentNullException>(!ReferenceEquals(array, null));
        Contract.Requires<ArgumentOutOfRangeException>(array.Length > Math.Abs(index) ||
                                                       (array.Length == 0 && index == 0));
        Contract.Requires<ArgumentOutOfRangeException>(removeCount >= 0 &&
                                                       (Math.Abs(index) + removeCount) < array.Length);

        T[] local = array;

        int addCount = ReferenceEquals(elements, null) ? 0 : elements.Length;

        if (index < 0) index = local.Length - index;

        var spliced = new T[local.Length - removeCount + addCount];

        var removed = new T[removeCount];


        if (index == 0)
        {
            if (removeCount == 0)
            {
                local.CopyTo(spliced, 0);
                if (addCount > 0)
                {
                    elements.CopyTo(spliced, local.Length);
                }
            }
            else
            {
                System.Array.Copy(local, removeCount, spliced, 0, local.Length - removeCount);
                if (addCount > 0)
                {
                    elements.CopyTo(spliced, local.Length - removeCount);
                }
            }
        }
        else
        {
            if (removeCount == 0 && addCount == 0)
                local.CopyTo(spliced, 0);

            else
            {
                System.Array.Copy(
                    local,
                    0,
                    spliced,
                    0,
                    index + 1);

                if (addCount > 0)
                {
                    elements.CopyTo(spliced, index);
                }

                System.Array.Copy(
                    local,
                    index + removeCount,
                    spliced,
                    index + addCount,
                    local.Length - index - removeCount
                    );
            }
        }

        array = local;
        return removed;
    }

    public static int Unshift<T>(ref T[] array, T element)
    {
        if (array.IsNullOrEmpty())
        {
            array = new T[1] {element};
            return 1;
        }
        var unshifted = new T[array.Length + 1];
        unshifted[0] = element;
        array.CopyTo(unshifted, 1);
        array = unshifted;
        return unshifted.Length;
    }

    public static int Unshift<T>(ref T[] array, params T[] elements)
    {
        if (array.IsNullOrEmpty())
        {
            if (elements.IsNullOrEmpty())
            {
                array = new T[0];
                return 0;
            }
            else
            {
                var elementsCopy = new T[elements.Length];
                elements.CopyTo(elementsCopy, 0);
                array = elementsCopy;
                return elementsCopy.Length;
            }
        }
        if (elements.IsNullOrEmpty())
        {
            return array.Length;
        }

        int total = array.Length + elements.Length;
        var unshifted = new T[total];
        elements.CopyTo(unshifted, 0);
        array.CopyTo(unshifted, elements.Length);
        array = unshifted;
        return total;
    }
}

Revisions

  1. Replaced all occurrences of Array.ConstrainedCopy with Array.Copy, thanks to @svick's suggestion
\$\endgroup\$
2
  • 1
    \$\begingroup\$ As a learning excercise, this is valid. But for production, Arrays are usually the wrong approach. \$\endgroup\$
    – Adam
    Commented Sep 21, 2012 at 8:45
  • \$\begingroup\$ @codesparkle, I am very aware of this. However, there are cases where arrays can be used safely, (such as within the internals of custom data structures). In any event, my reason for posting this is to try to learn more about proper use of arrays, as I am already very competent with more production friendly C# alternatives. One problem is that not every language offers the same high-level richness that C# does, and I think it is bad programming practice to abuse our tools to the point that they restrict our programming paradigm. \$\endgroup\$ Commented Sep 21, 2012 at 8:49

1 Answer 1

3
\$\begingroup\$

Your code seems fine to me, just a few things I would do differently:

  1. Your usage of ConstrainedCopy(), instead of normal Copy(), doesn't make sense to me. This method is useful if you want to guarantee that the target array is in a consistent state even if an error happens during processing. But you're always modifying a temporary array, so you don't care about its state after an error.

    Using ConstrainedCopy() will be less performant (although probably not significantly) and it's more confusing (it makes me think why you didn't use normal Copy()).

  2. When creating an array using array initializer, you don't need to specify the size: new T[] {element} works fine.

Also, if you used your methods a lot, your application would have bad performance because of all that copying. If you ever wanted to actually use something like this, you should do what List<T> does: when adding, resize to twice the size of the original array, so that you don't have to copy the whole array the next time you add to it. But you're probably aware of this.

\$\endgroup\$
4
  • \$\begingroup\$ +1 Thanks - Re # 1, I did not realize that Array.Copy had overloads with the same signature as Array.ConstrainedCopy, and I incorrectly assumed that ConstrainedCopy was used to set bounds as to what elements were copied \$\endgroup\$ Commented Sep 21, 2012 at 9:31
  • \$\begingroup\$ Re #2, I am aware of this, but I'm pretty sure the compiler ends up emitting the same IL regardless. - Just validated this with LINQPad \$\endgroup\$ Commented Sep 21, 2012 at 9:33
  • \$\begingroup\$ Yes, but IL is not all that matters. And not having to specify the size means shorter code, better readability and better maintainability, even if it is by a tiny little bit. \$\endgroup\$
    – svick
    Commented Sep 21, 2012 at 10:21
  • \$\begingroup\$ That's a fair point. I'm pretty comfortable with my understanding of code readability and API design, so I'm more concerned about the actual functionality of the code in this sample, but I would agree that what you say is valid. \$\endgroup\$ Commented Sep 21, 2012 at 12:58

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.