9

この署名を持つ単純なデータ クラスがあります。

internal interface IMyClass {
    string Letter { get; }
    int Number { get; }
}

string sortFieldフィールド ( として指定) と方向 ( として指定)bool isAscendingに基づいて、このデータをソートできるようにしたいと考えています。

現在、私はを使用していswitchます(各ケース内の昇順ロジックを としてif

IEnumerable<IMyClass> lst = new IMyClass[];//provided as paramater
switch (sortField)
{
    case "letter":
        if( isAscending ) {
            lst = lst.OrderBy( s => s.Letter );
        } else {
            lst = lst.OrderByDescending( s => s.Letter );
        }
        break;
    case "number":
        if( isAscending ) {
            lst = lst.OrderBy( s => s.Number );
        } else {
            lst = lst.OrderByDescending( s => s.Number );
        }
        break;
}

s => s.Number2 つのプロパティの場合、これはかなり醜いですが、並べ替えロジックが異なると問題になります (コード内で が 2 回重複していることもわかります)。

質問 ブール値を渡してソート方向を選択する最良の方法は何ですか?

System.Core.dll をバラバラにして 、OrderBy 拡張メソッドの実装を見つけました。

注文方法:

public static IOrderedEnumerable<TSource> OrderBy<TSource, TKey>(
        this IEnumerable<TSource> source, 
        Func<TSource, TKey> keySelector
    ){

    return new OrderedEnumerable<TSource, TKey>(
        source, 
        keySelector, 
        null, 
        false
    );
}

降順:

public static IOrderedEnumerable<TSource> OrderByDescending<TSource, TKey>(
        this IEnumerable<TSource> source, 
        Func<TSource, TKey> keySelector
    ){
        return new OrderedEnumerable<TSource, TKey>(
            source, 
            keySelector, 
            null, 
            true
        );
}

2 つの名前付きメソッドを持つ目的は、このブール値を抽象化することです。System.Core の内部にあるように、独自の拡張機能を簡単に作成することはできませOrderedEnumberableん。bool -> methodName -> bool から移動するレイヤーを作成することは、私には間違っているようです。

4

5 に答える 5

18

私はあなた自身の拡張メソッドを書くと思います:

public static IEnumerable<T> Order<T, TKey>(this IEnumerable<T> source, Func<T, TKey> selector, bool ascending)
{
    if (ascending)
    {
        return source.OrderBy(selector);
    }
    else
    {
        return source.OrderByDescending(selector);
    }
}

次に、次のように書くことができます。

lst = lst.Order( s => s.Letter, isAscending );

メソッド名の指定に関しては、これが答えとして出てこないことを願っていますが、文字列を渡す代わりにセレクター関数を使用することに固執する必要があると思います。文字列ルートに行っても、実際にはタイピングが節約されたり、明確さが向上したりするわけではなく ( ?"letter"よりもはるかに高速または明確s => s.Letterです)、コードが太くなるだけです (文字列からセレクター関数への何らかのマッピングを維持するか、次のように記述する必要があります)。それらの間で変換するためのカスタム解析ロジック)、おそらくより脆弱です (後者のルートに行くと、バグの可能性がかなり高くなります)。

もちろん、ユーザー入力から文字列を取得して並べ替えをカスタマイズすることを意図している場合は、選択の余地がないため、私の落胆的な発言を無視してください。


編集:ユーザー入力を受け入れているので、マッピングの意味は次のとおりです。

class CustomSorter
{
    static Dictionary<string, Func<IMyClass, object>> Selectors;

    static CustomSorter()
    {
        Selectors = new Dictionary<string, Func<IMyClass, object>>
        {
            { "letter", new Func<IMyClass, object>(x => x.Letter) },
            { "number", new Func<IMyClass, object>(x => x.Number) }
        };
    }

    public void Sort(IEnumerable<IMyClass> list, string sortField, bool isAscending)
    {
        Func<IMyClass, object> selector;
        if (!Selectors.TryGetValue(sortField, out selector))
        {
            throw new ArgumentException(string.Format("'{0}' is not a valid sort field.", sortField));
        }

        // Using extension method defined above.
        return list.Order(selector, isAscending);
    }
}

上記は明らかに、文字列から式を動的に生成して呼び出すほど巧妙ではありません。そして、それは、あなたの好みや所属しているチームや文化によって、強みにも弱みにもなる可能性があります。この特定のケースでは、動的表現ルートが過剰に設計されているように感じられるため、手動マッピングに投票すると思います。

于 2012-08-02T16:41:55.747 に答える
2

最後にさらにメソッドを追加する場合は、IOrderedEnumerableを返すことをお勧めします。このようにして、コンパイラはチェーン全体を1つの式としてコンパイルします。

public static class OrderByWithBooleanExtension
{
    public static IOrderedEnumerable<TSource> OrderBy<TSource, TKey>(this IEnumerable<TSource> source, Func<TSource, TKey> keySelector, bool isAscending)
    {
        return isAscending ? source.OrderBy(keySelector) : source.OrderByDescending(keySelector);
    }
}
于 2012-08-02T16:45:32.767 に答える
1

ここでScottGuによって説明されている動的なlinqオプションを見てみましょう。

于 2012-08-02T16:44:21.283 に答える
1

次のように、文字列プロパティで並べ替え、ブール値による昇順と降順をサポートする独自の拡張機能を実装できます。

public static IOrderedQueryable<T> OrderByProperty<T>(this IQueryable<T> query, string memberName, bool ascending = true)
{
    var typeParams = new[] { Expression.Parameter(typeof(T), "") };

    var pi = typeof(T).GetProperty(memberName);
    string operation = ascending ? "OrderBy" : "OrderByDescending";
    return (IOrderedQueryable<T>)query.Provider.CreateQuery(
        Expression.Call(
            typeof(Queryable),
            operation,
            new[] { typeof(T), pi.PropertyType },
            query.Expression,
            Expression.Lambda(Expression.Property(typeParams[0], pi), typeParams))
    );
}
于 2012-08-02T16:58:21.377 に答える
0

Func正しい操作を選択する を作成できます。

var orderBy = isAscending ? (Func<Func<IMyClass, object>, IOrderedEnumerable<IMyClass>>)lst.OrderBy : lst.OrderByDescending;
switch (sortField)
{
    case "letter":
        lst = orderBy(s => s.Letter);
        break;
    case "number":
        lst = orderBy(s => s.Number);
        break;
}

CraftyFella が提案したように動的 LINQと組み合わせると、次のようになります。

var orderBy = isAscending ? (Func<Func<IMyClass, object>, IOrderedEnumerable<IMyClass>>)lst.OrderBy : lst.OrderByDescending;
lst = orderBy(mySortCriteria);

または、必要に応じて、1 つの長い行を指定します。

lst = (isAscending ? (Func<Func<IMyClass, object>, IOrderedEnumerable<IMyClass>>)lst.OrderBy : lst.OrderByDescending)(mySortCriteria);

私はダンタオの解決策を私のものよりも好むと思います。あなたが役に立つと思った場合に備えて、これを捨てようと思いました.

于 2012-08-02T16:52:12.960 に答える