5

Func<T> TResult既存の LINQ 関数を変更して関数のシグネチャに追加する方法、つまり のようにセレクターを使用できるようにする方法を知りたいと思ってい(o => o.CustomField)ます。

たとえば、C# では、.IsDistinct()整数のリストが異なるかどうかを確認するために使用できます。.IsDistinctBy(o => o.SomeField)フィールド内の整数o.SomeFieldが異なるかどうかを確認するために使用することもできます。舞台裏で.IsDistinctBy(...)、関数シグネチャのようなものがFunc<T> TResult追加されていると思いますか?

私の質問はこれです: 既存の LINQ 拡張関数を取得し、それを変換してパラメーターを持つことができるようにする手法は何(o => o.SomeField)ですか?

ここに例があります。

この拡張関数は、リストが単調に増加しているかどうかを確認します (つまり、値が 1,1,2,3,4,5,5 のように減少することはありません)。

main()
{
   var MyList = new List<int>() {1,1,2,3,4,5,5};
   DebugAssert(MyList.MyIsIncreasingMonotonically() == true);
}

public static bool MyIsIncreasingMonotonically<T>(this List<T> list) where T : IComparable
{
    return list.Zip(list.Skip(1), (a, b) => a.CompareTo(b) <= 0).All(b => b);
}

「By」を追加したい場合は、パラメーターを追加しますFunc<T> TResult。しかし、関数の本体を変更して選択するようにするにはどうすればよい(o => o.SomeField)ですか?

main()
{
   DebugAssert(MyList.MyIsIncreasingMonotonicallyBy(o => o.CustomField) == true);
}

public static bool MyIsIncreasingMonotonicallyBy<T>(this List<T> list, Func<T> TResult) where T : IComparable
{
    // Question: How do I modify this function to make it  
    // select by o => o.CustomField?
    return list.Zip(list.Skip(1), (a, b) => a.CompareTo(b) <= 0).All(b => b);
}
4

3 に答える 3

4

指定された を 1 回だけ列挙する、次のような実装を検討してくださいIEnumerable<T>。列挙には副作用がある可能性があり、呼び出し元は通常、可能であれば単一のパススルーを期待します。

public static bool IsIncreasingMonotonically<T>(
    this IEnumerable<T> _this)
    where T : IComparable<T>
{
    using (var e = _this.GetEnumerator())
    {
        if (!e.MoveNext())
            return true;
        T prev = e.Current;
        while (e.MoveNext())
        {
            if (prev.CompareTo(e.Current) > 0)
                return false;
            prev = e.Current;
        }
        return true;
    }
}

あなたenumerable.IsIncreasingMonotonicallyBy(x => x.MyProperty)が説明するオーバーロードは、次のように記述できるようになりました。

public static bool IsIncreasingMonotonicallyBy<T, TKey>(
    this IEnumerable<T> _this,
    Func<T, TKey> keySelector)
    where TKey : IComparable<TKey>
{
    return _this.Select(keySelector).IsIncreasingMonotonically();
}
于 2013-02-13T19:57:15.470 に答える
3

Funcを a と b に適用するだけです:

public static bool MyIsIncreasingMonotonicallyBy<T, TResult>(this IEnumerable<T> list, Func<T, TResult> selector)
    where TResult : IComparable<TResult>
{
    return list.Zip(list.Skip(1), (a, b) => selector(a).CompareTo(selector(b)) <= 0).All(b => b);
}
于 2013-02-13T19:15:27.327 に答える
0

上の 1 つは右に近いですが、問題があります。

  1. リストには IEnumeration の複数の列挙が含まれる可能性があります

    public static bool MyIsIncreasingMonotonicallyBy<T, TResult>(
            this IEnumerable<T> list, Func<T, TResult> selector)
        where TResult : IComparable<TResult>
    {
        var enumerable = list as IList<T> ?? list.ToList();
        return enumerable.Zip(
                   enumerable.Skip(1),
                   (a, b) => selector(a).CompareTo(selector(b)) <= 0
               ).All(b => b);
    }
    

PS拡張メソッドは非ジェネリックでネストされていない静的クラスでのみ宣言できるため、「this」を削除する必要があると思います。


フレデリック・ハミディへの返答:

次の点を考慮してください。

IEnumerable<string> names = GetNames();
foreach (var name in names)   Console.WriteLine("Found " + name);
var allNames = new StringBuilder();
foreach (var name in names)   allNames.Append(name + " ");

GetNames() が IEnumerable を返すと仮定すると、2 つの foreach ステートメントでこのコレクションを 2 回列挙することで、実質的に余分な作業を行っています。GetNames() の結果がデータベース クエリになる場合、そのクエリを 2 回実行することになりますが、どちらも同じデータを取得します。

この種の問題は簡単に修正できます。変数の初期化の時点で、シーケンスをリストに変換することによって列挙を強制するだけです (または配列にすることもできます)。配列型とリスト型の両方が IEnumerable インターフェイスを実装します。

于 2013-02-13T19:33:45.170 に答える