4

So basically I wrote my little Add extension method for array types.

using System;
using System.Linq;
public static class Extensions 
{
    public static void Add<T>(this T[] _self, T item)
    {
        _self = _self.Concat(new T[] { item }).ToArray();
    }
}
public class Program
{
    public static void Main()
    {
        string[] test = { "Hello" };
        test = test.Concat(new string[] { "cruel" }).ToArray();
        test.Add("but funny");
        Console.WriteLine(String.Join(" ", test) + " world");
    }
}

The output should be Hello cruel but funny world, but the but funny will never be concatenated within the extension method.

Editing the same array in the extension seems not to work, too:

using System;
using System.Linq;
public static class Extensions 
{
    public static void Add<T>(this T[] _self, T item)
    {
        Array.Resize(ref _self, _self.Length + 1);
        _self[_self.Length - 1] = item;
    }
}
public class Program
{
    public static void Main()
    {
        string[] test = { "Hello" };
        test = test.Concat(new string[] { "cruel" }).ToArray();
        test.Add("but funny");
        Console.WriteLine(String.Join(" ", test) + " world");
    }
}

What did I get wrong here, how can I use it as extension?

.dotNet fiddle: https://dotnetfiddle.net/9os8nY or https://dotnetfiddle.net/oLfwRD

(It would be great to find a way so I can keep the call test.Add("item");)

Martin Braun
  • 10,906
  • 9
  • 64
  • 105
  • Can't be done with an extension method because this and ref kewords can't be used together. You can return the new array as result and use that further on. – John Jan 06 '15 at 13:12
  • 1
    You should really use `List<>` instead and if you need array at some point simply do a `.ToArray()`. You are trying to do what exactly `List<>` is already doing. – Franck Jan 06 '15 at 13:14
  • 1
    @Franck I'm working with Unity3D, the editor handles arrays the better way (in fact it won't handle List<> at all), and for performance reasons they should stay arrays, as such extension methods will be only called in the editor, not the game itself. – Martin Braun Jan 06 '15 at 13:18
  • @modiX You should add Unity tag to the question in that case. `List<>` according to Microsoft Source Code actually use an Array to store data so `List` is the same thing as `string[]`. Difference would be allocation size for a handful extra properties. Access to data is the same speed and adding is optimized by Microsoft themselves. – Franck Jan 06 '15 at 13:34

3 Answers3

6

You are assigning a new reference to the argument,it won't change the actual array unless you pass it as ref parameter. Since this is an extension method it's not an option. So consider using a normal method:

public static void Add<T>(ref T[] _self, T item)
{
    _self = _self.Concat(new T[] { item }).ToArray();
}

Add(ref test, "but funny");

Or if you insist on using extension methods you need to make the array second parameter in order to use ref:

public static void AddTo<T>(this T item, ref T[] arr, )
{
    arr = arr.Concat(new T[] { item }).ToArray();
}

"but funny".AddTo(ref test);

Array.Resize doesn't work. Because it changes the _self, not the test array. Now when you pass a reference type without ref keyword, the reference is copied. It's something like this:

string[] arr1 = { "Hello" };
string[] arr2 = arr1;

Now, if you assign a new reference to arr2, it will not change arr1's reference.What Array.Resize is doing is that since resizing an array is not possible, it creates a new array and copies all elements to a new array and it assigns that new reference to the parameter (_self in this case).So it changes the where _self points to but since _self and test are two different references (like arr1 and arr2), changing one of them doesn't affect the other.

On the other hand if you pass array as ref to your method as in my first example, the Array.Resize will also work as expected, because in this case the reference will not be copied:

public static void Add<T>(ref T[] _self, T item)
{
    Array.Resize(ref _self, _self.Length + 1);
    _self[_self.Length - 1] = item;
}
Selman Genç
  • 100,147
  • 13
  • 119
  • 184
  • I updated my question, because the other way using `Array.Resize` does not work either. I don't get why the reference to `_self` behaves that way. Using the `ref` keyword shouldn't be necessary, because it's call by reference, already. It would be great to find a solution I can use the extension method like the `Add` method on lists. I.e. `test.Add("item");`. Any idea? – Martin Braun Jan 06 '15 at 13:16
  • @modiX I added explanation about why it doesnt work. if there is anything not clear let me know – Selman Genç Jan 06 '15 at 13:22
  • Thank you, so when it changes `_self` it changes a variable only in the scope of the `Add` extension method. This assumes, `_self` is passed as value, not as reference. But that is the point. `Array` should be called by reference by default, why are they not into extension methods? – Martin Braun Jan 06 '15 at 13:26
  • @modiX you should know that reference type variables, are not holding actual value, they hold an address in the memory where the actual object can be found. once you understand that you will know if you pass a reference type argument, it's reference is copied to another variable. like in my arr1 and arr2 example. So, if I change where arr2 points to, it won't affect arr1, since they are completely different variables. – Selman Genç Jan 06 '15 at 13:28
  • modiX - imagine you're not writing an extension method, but an actual method on the `Array` type. What you're doing is saying `this = ...`. That's not valid: you need to return the new instance, or modify yourself. Since Arrays are immutable, you can't modify yourself. – Kieren Johnstone Jan 06 '15 at 13:29
  • @Selman22 Wow I was a victim of my own fallacy. I was expecting the array is passed by reference so I can override the value behind the reference by reassigning it, but this was wrong as it leads to a new reference by passing by value. Thank you guys. =) – Martin Braun Jan 06 '15 at 13:38
1

I believe the _self = creates a copy of the _self object into the local variable created for the parameter _self - and so the original will not be updated. Use a reference type like a list, or create a static method which returns the new array.

In terms of the 'feel' of the reason: you're calling a method on a variable instance - you can't change that instance from within the code executing under the context of it

Kieren Johnstone
  • 41,277
  • 16
  • 94
  • 144
  • Sorry, yes not sure what I meant by that either. The point is you're overwriting the local variable `_self`, not updating the source expression which invoked the method – Kieren Johnstone Jan 06 '15 at 13:27
  • Ah - I mean immutable. Array.Resize doesn't modify the array, it returns a new resized one AFAIK – Kieren Johnstone Jan 06 '15 at 13:29
-1

You may change code like this:

public static class Extensions 
{
    public static T[] Add<T>(this T[] _self, T item)
    {
        return _self.Concat(new T[] { item }).ToArray();
    }
}
public class Program
{
    public static void Main()
    {
        string[] test = { "Hello" };
        test = test.Concat(new string[] { "cruel" }).ToArray();
        test = test.Add("but funny");
        Console.WriteLine(String.Join(" ", test) + " world");
    }
}

As a side note - usage will be same as Concat method.

Renatas M.
  • 11,694
  • 1
  • 43
  • 62
  • Using this is not good because it violates the expected behaviour of similar classes in the framework that Add() modifies the object itself. Very easy for someone to use that method falsely. At least give the method a different name (like Concat) which makes clear that the object itself is not modified. – NineBerry Feb 23 '17 at 10:05
  • @NineBerry OP decided to name it like this. – Renatas M. Feb 23 '17 at 10:23