1

Using this answer to the question "How to add a string to a string[] array? There's no .Add function" I am trying to use this answer to write an generic extension to append elements to an generic array. Only using the Array.Resize() method works well and the sample below adds an extra element to my string array

string[] array = new string[] { "Foo", "Bar" };
Array.Resize(ref array, array.Length + 1);
array[array.Length - 1] = "Baz";

But when I try to use the ArrayExtension method described below, the method does resize my array inside the method but when it returns the array is unchanged?

My extension class

public static class ArrayExtensions
{
    public static void Append<T>(this T[] array, T append)
    {
        Array.Resize(ref array, array.Length + 1);
        array[array.Length - 1] = append;    // < Adds an extra element to my array
    }
}

Used as follows

string[] array = new string[] { "Foo", "Bar" };
array.Append("Baz");

When the method returns, the added element does not exist. What am I missing?

UPDATE

As pointed out, this question has been asked and answered here before. I will accept the previous question "Changing size of array in extension method does not work?" as an answer to my question.

UPDATE 2

Since if a return method is used in the extension method it does violate the expected behaviour of similar classes in the framework that Append() to modify the object itself, I did some changes to the extension method to prevent it from being falsely used. Thanks @NineBerry for pointing this out.

I also added params T[] add to allow for adding multiple elements at once.

public static class ArrayExtensions
{
    /// <summary>
    /// Creates a copy of an object array and adds the extra elements to the created copy
    /// </summary>
    /// <typeparam name="T"></typeparam>
    /// <param name="array"></param>
    /// <param name="add">Elemet to add to the created copy</param>
    /// <returns></returns>
    public static T[] CopyAddElements<T>(this T[] array, params T[] add)
    {
        for (int i = 0; i < add.Length; i++)
        {
            Array.Resize(ref array, array.Length + 1);
            array[array.Length - 1] = add[i];
        }
        return array;
    }
}

Usage

string[] array = new string[] { "Foo", "Bar" };
array = array.CopyAddElements("Baz", "Foobar");
for (int i = 0; i < array.Length; i++)
{
    System.Console.Write($"{array[i]} ");
}

/* Output
 *  Foo Bar Baz Foobar
*/
Community
  • 1
  • 1
user5825579
  • 105
  • 3
  • 15
  • Array.Resize is a deception. It's really a copy to a new object. And from there it follows that you need a ref parameter to have an effect on an actual argument. – Tom Blodget Feb 23 '17 at 11:59

3 Answers3

5

Array.Resize creates a new array. That's why you have to pass the array using the ref keyword to Array.Resize. So, after returning from Array.Resize, the variable array references a different object.

There is no way to use an extension method to do what you are trying to do.

Why not simply use a List<> instead of an array? Using Array.Resize is a very costly operation. It means allocating new memory and copying over the data from the old array to the new array each time it is called to increase the array length.

NineBerry
  • 26,306
  • 3
  • 62
  • 93
4

What actually happens is that

array.Append("Baz");

Is translated to

ArrayExtensions.Append( array, "Baz" );

Which means that reference to array is passed in by value, so inside the Append method you work with a copy of the reference. The Array.Resize method then takes this new variable as ref, creates a new array in memory and changes the variable to point to it. Unfortunately, this changes just this variable and not the original one.

You can return the newly created array as return value or create a static method that uses ref instead of extension methods.

Martin Zikmund
  • 38,440
  • 7
  • 70
  • 91
2

Contrary to your intuition the this parameter is not passed with an implicit ref and it is also not allowed to add a ref to it. See also the discussion here

Community
  • 1
  • 1
TToni
  • 9,145
  • 1
  • 28
  • 42