tags:

views:

143

answers:

4

I recently started to learn about LINQ and came across the OrderBy extension method. This initially excited me because the syntax seemed much nicer than the Sort method that we use all over the place for Generic Lists.

For example, when sorting a list of languages, we normally do something like:

neutralCultures.Sort((x, y) => x.EnglishName.CompareTo(y.EnglishName));

This works well, but I prefer the syntax of OrderBy, which only requires that you pass in the property that you wish to sort by, like so:

neutralCultures.OrderBy(ci => ci.EnglishName);

The problem is that OrderBy returns IOrderedEnumerable, but I need List<T>. So, I set out to extend the Sort method for List<T> using the same signature that is used for OrderBy.

public static void Sort<TSource, TKey>(this List<TSource list, Func<TSource, TKey> keySelector)
{
    list = list.OrderBy(keySelector).ToList<TSource>();
}

This would be called like:

neutralCultures.Sort(ci => ci.EnglishName);

Maybe I'm overlooking a simple implementation detail, but this code doesn't work. It compiles, but doesn't return an ordered list. I'm sure I can refactor this to make it work, but I was just wondering why setting list within the extension method doesn't work.

+2  A: 

I think it is because list is not passed by ref. Thus setting the list variable to another object doesn't alter where the original variable was pointing.

You might be able to do this (haven't tested it properly):

public static void Sort<TSource, TKey>(this List<TSource> list, Func<TSource, TKey> keySelector)
    {
        var tempList = list.OrderBy(keySelector).ToList<TSource>();
        list.Clear();
        list.AddRange(tempList);
    }
Øyvind Skaar
Nice thought, but I just tried adding the "ref" keyword, and it doesn't work with "this".
ern
No, I don't think you can do that.
Øyvind Skaar
Thanks a lot. This is exactly where I was going next with this.
ern
+3  A: 

I've written Sort variants using a selector before - it isn't hard... something like:

using System;
using System.Collections.Generic;
class Foo
{
    public string Bar { get; set; }
}

static class Program
{
    static void Main()
    {
        var data = new List<Foo> {
            new Foo {Bar = "def"},
            new Foo {Bar = "ghi"},
            new Foo {Bar = "abc"},
            new Foo {Bar = "jkl"}
        };
        data.Sort(x => x.Bar);
        foreach (var item in data)
        {
            Console.WriteLine(item.Bar);
        }
    }

    static void Sort<TSource, TValue>(
        this List<TSource> source,
        Func<TSource, TValue> selector)
    {
        var comparer = Comparer<TValue>.Default;
        source.Sort((x,y) => comparer.Compare(selector(x), selector(y)));
    }

}
Marc Gravell
note it is pretty trivial to add an overload that accepts an `IComparer<TValue>` for custom comparer support.
Marc Gravell
+2  A: 

That's the behaviour I would expect. Imagine if such a thing were possible:

var c = myCustomer;
myCustomer.DoStuff();
if (c == myCustomer) // returns false!!!

Simply calling a method on an object (which is what an extension method looks like to the user of your framework) shouldn't change which instance the reference points to.

For your example, I would stick with Sort.

Matt Hamilton
Note that this reasoning only applies to classes; structs *can* behave exactly like you state, even if they are immutable. The DoStuff() method (for a struct) can be `this = new Foo("mwahahaha");`
Marc Gravell
Oh wow I didn't know that one, Marc. That's *ugly*.
Matt Hamilton
Thanks Matt, makes a lot more sense now. Thanks for simplifying it for me.
ern
A: 

You are replacing the value of the local parameter variable called list, not changing the value of the caller's variable (which is what you are trying to do.)

Andrew Kennan