收录日期:2019/09/19 00:17:42 时间:2009-01-11 22:53:16 标签:c#,.net,linq

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.

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.

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)));
    }

}

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);
    }

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.)