views:

115

answers:

3

I want to be able to write extension methods so that I can say:

lines.ForceSpaceGroupsToBeTabs();

instead of:

lines = lines.ForceSpaceGroupsToBeTabs();

However, the following code currently outputs:

....one
........two

instead of:

Tone
TTtwo

What do I have to change in the following code to make it output:

Tone
TTtwo

(note that for visibility, . = space, T = \t):

using System;
using System.Collections.Generic;

namespace TestExtended82343
{
    class Program
    {
        static void Main(string[] args)
        {
            List<string> lines = new List<string>();
            lines.Add("....one");
            lines.Add("........two");

            lines.ForceSpaceGroupsToBeTabs();

            lines.ForEach(l => Console.WriteLine(l));
            Console.ReadLine();
        }
    }

    public static class Helpers
    {
        public static void ForceSpaceGroupsToBeTabs(this List<string> originalLines)
        {
            string spaceGroup = new String('.', 4);
            List<string> lines = new List<string>();
            foreach (var originalLine in originalLines)
            {
                lines.Add(originalLine.Replace(spaceGroup, "T"));
            }
            originalLines = lines;
        }
    }
}
+11  A: 

You have to modify the contents of the List<string> passed to the extension method, not the variable that holds the reference to the list:

public static void ForceSpaceGroupsToBeTabs(this List<string> lines)
{
    string spaceGroup = new String('.', 4);
    for (int i = 0; i < lines.Count; i++)
    {
        lines[i] = lines[i].Replace(spaceGroup, "T");
    }
}
dtb
Indeed. You can't switch the "extended" referenced object, but you can change one's contents
NoProblemBabe
+6  A: 

You'd have to change the contents of the original list - just reassigning the parameter to have a different value isn't going to do it. Something like this:

public static void ForceSpaceGroupsToBeTabs(this List<string> lines)
{
    string spaceGroup = new String('.', 4);
    for (int i = 0; i < lines.Count; i++)
    {
        lines[i] = lines[i].Replace(spaceGroup, "T");
    }
}

It's worth noting that this has nothing to do with extension methods. Imagine you'd just called:

Helpers.ForceSpaceGroupsToBeTabs(lines);

... because that's what the code is effectively translated into. There's nothing special about the fact that it's an extension method; if you change the code so that the "normal" static method will work, then it'll work as an extension method too. As noted in the comments, one thing you can't do with an extension method is make the first parameter a ref parameter.

(EDIT: I realise this is the exact same code that dtb posted, although we arrived there independently. I'm keeping this answer anyway, as it's got more than code.)

Jon Skeet
it's not the exact same code, you have spaceGroup("T"), were you thinking of something else or was that just a typo (it doesn't compile)
Edward Tanguay
@Jon: The difference is, that with Helpers.ForceSpaceGroupsToBeTabs he could change the parameter to be a "ref" parameter, in which case his existing code *would* have the desired effect. With extension methods though, you can't make the param ref...
BFree
@BFree: That's true - will edit.
Jon Skeet
@Edward: Just a typo. Will fix.
Jon Skeet
+1  A: 

If it's a reference type, you'd have to change it's contents. If it's a value type you're passing in, you're out of luck. The very existence of extension methods is put into place to support functional paradigms in C#, and those functional paradigms, by their very essence, tend towards immutability of types, hence the inability to change the value off of which the extension method is called.

In other words, while you could do it, it may not be in keeping with the "spirit" of functional programming.

David Morton
I think the second part of this answer is misleading. The overwhelming use case for extension methods is on interfaces, which are reference types, and in those cases there is no special limit on mutation: the capabilities are identical to ordinary methods, in that `a.SomeMethod();` cannot make `a` refer to something else, but can change the object that it refers to.
Daniel Earwicker