web-dev-qa-db-ja.com

明示的な型コンパレータをインラインで指定できますか?

したがって、.NET 3.0/3.5は、LINQで提供されるすてきな関数のおかげで、データのクエリ、並べ替え、および操作を行うための多くの新しい方法を提供します。場合によっては、組み込みの比較演算子を持たないユーザー定義型を比較す​​る必要があります。多くの場合、比較は本当に簡単です-foo1.key?= foo2.keyのようなものです。型の新しいIEqualityComparerを作成するのではなく、匿名のデリゲート/ラムダ関数を使用してインラインで単純に比較を指定できますか?何かのようなもの:

var f1 = ...,
    f2 = ...;
var f3 = f1.Except(
           f2, new IEqualityComparer(
             (Foo a, Foo b) => a.key.CompareTo(b.key)
           ) );

上記は実際には機能しないと確信しています。リンゴとリンゴを比較する方法をプログラムに伝えるためだけに、クラス全体として「重い」何かを作成する必要はありません。

55
Coderer

My MiscUtil ライブラリには、投影デリゲートからIComparer <T>を構築するProjectionComparerが含まれています。 ProjectionEqualityComparerで同じことをするのは10分の作業です。

編集:ProjectionEqualityComparerのコードは次のとおりです。

using System;
using System.Collections.Generic;

/// <summary>
/// Non-generic class to produce instances of the generic class,
/// optionally using type inference.
/// </summary>
public static class ProjectionEqualityComparer
{
    /// <summary>
    /// Creates an instance of ProjectionEqualityComparer using the specified projection.
    /// </summary>
    /// <typeparam name="TSource">Type parameter for the elements to be compared</typeparam>
    /// <typeparam name="TKey">Type parameter for the keys to be compared,
    /// after being projected from the elements</typeparam>
    /// <param name="projection">Projection to use when determining the key of an element</param>
    /// <returns>A comparer which will compare elements by projecting 
    /// each element to its key, and comparing keys</returns>
    public static ProjectionEqualityComparer<TSource, TKey> Create<TSource, TKey>(Func<TSource, TKey> projection)
    {
        return new ProjectionEqualityComparer<TSource, TKey>(projection);
    }

    /// <summary>
    /// Creates an instance of ProjectionEqualityComparer using the specified projection.
    /// The ignored parameter is solely present to aid type inference.
    /// </summary>
    /// <typeparam name="TSource">Type parameter for the elements to be compared</typeparam>
    /// <typeparam name="TKey">Type parameter for the keys to be compared,
    /// after being projected from the elements</typeparam>
    /// <param name="ignored">Value is ignored - type may be used by type inference</param>
    /// <param name="projection">Projection to use when determining the key of an element</param>
    /// <returns>A comparer which will compare elements by projecting
    /// each element to its key, and comparing keys</returns>
    public static ProjectionEqualityComparer<TSource, TKey> Create<TSource, TKey>
        (TSource ignored,
         Func<TSource, TKey> projection)
    {
        return new ProjectionEqualityComparer<TSource, TKey>(projection);
    }

}

/// <summary>
/// Class generic in the source only to produce instances of the 
/// doubly generic class, optionally using type inference.
/// </summary>
public static class ProjectionEqualityComparer<TSource>
{
    /// <summary>
    /// Creates an instance of ProjectionEqualityComparer using the specified projection.
    /// </summary>
    /// <typeparam name="TKey">Type parameter for the keys to be compared,
    /// after being projected from the elements</typeparam>
    /// <param name="projection">Projection to use when determining the key of an element</param>
    /// <returns>A comparer which will compare elements by projecting each element to its key,
    /// and comparing keys</returns>        
    public static ProjectionEqualityComparer<TSource, TKey> Create<TKey>(Func<TSource, TKey> projection)
    {
        return new ProjectionEqualityComparer<TSource, TKey>(projection);
    }
}

/// <summary>
/// Comparer which projects each element of the comparison to a key, and then compares
/// those keys using the specified (or default) comparer for the key type.
/// </summary>
/// <typeparam name="TSource">Type of elements which this comparer 
/// will be asked to compare</typeparam>
/// <typeparam name="TKey">Type of the key projected
/// from the element</typeparam>
public class ProjectionEqualityComparer<TSource, TKey> : IEqualityComparer<TSource>
{
    readonly Func<TSource, TKey> projection;
    readonly IEqualityComparer<TKey> comparer;

    /// <summary>
    /// Creates a new instance using the specified projection, which must not be null.
    /// The default comparer for the projected type is used.
    /// </summary>
    /// <param name="projection">Projection to use during comparisons</param>
    public ProjectionEqualityComparer(Func<TSource, TKey> projection)
        : this(projection, null)
    {
    }

    /// <summary>
    /// Creates a new instance using the specified projection, which must not be null.
    /// </summary>
    /// <param name="projection">Projection to use during comparisons</param>
    /// <param name="comparer">The comparer to use on the keys. May be null, in
    /// which case the default comparer will be used.</param>
    public ProjectionEqualityComparer(Func<TSource, TKey> projection, IEqualityComparer<TKey> comparer)
    {
        if (projection == null)
        {
            throw new ArgumentNullException("projection");
        }
        this.comparer = comparer ?? EqualityComparer<TKey>.Default;
        this.projection = projection;
    }

    /// <summary>
    /// Compares the two specified values for equality by applying the projection
    /// to each value and then using the equality comparer on the resulting keys. Null
    /// references are never passed to the projection.
    /// </summary>
    public bool Equals(TSource x, TSource y)
    {
        if (x == null && y == null)
        {
            return true;
        }
        if (x == null || y == null)
        {
            return false;
        }
        return comparer.Equals(projection(x), projection(y));
    }

    /// <summary>
    /// Produces a hash code for the given value by projecting it and
    /// then asking the equality comparer to find the hash code of
    /// the resulting key.
    /// </summary>
    public int GetHashCode(TSource obj)
    {
        if (obj == null)
        {
            throw new ArgumentNullException("obj");
        }
        return comparer.GetHashCode(projection(obj));
    }
}

次に、使用例を示します。

var f3 = f1.Except(f2, ProjectionEqualityComparer<Foo>.Create(a => a.key));
63
Jon Skeet

ここにあなたがしたいことをすべきシンプルなヘルパークラスがあります

public class EqualityComparer<T> : IEqualityComparer<T>
{
    public EqualityComparer(Func<T, T, bool> cmp)
    {
        this.cmp = cmp;
    }
    public bool Equals(T x, T y)
    {
        return cmp(x, y);
    }

    public int GetHashCode(T obj)
    {
        return obj.GetHashCode();
    }

    public Func<T, T, bool> cmp { get; set; }
}

次のように使用できます。

processed.Union(suburbs, new EqualityComparer<Suburb>((s1, s2)
    => s1.SuburbId == s2.SuburbId));
19
mike

IEnumerableで追加のヘルパーを提供することは、これを行うためのよりクリーンな方法です。

参照: この質問

だからあなたが持つことができます:

var f3 = f1.Except(
           f2, 
             (a, b) => a.key.CompareTo(b.key)
            );

拡張メソッドを適切に定義する場合

8
Sam Saffron

このプロジェクトは似たようなことをします: AnonymousComparer-Linqのラムダ比較セレクター .

6
Jeremy Thomas

次のようなものはなぜですか:

    public class Comparer<T> : IEqualityComparer<T>
    {
        private readonly Func<T, T, bool> _equalityComparer;

        public Comparer(Func<T, T, bool> equalityComparer)
        {
            _equalityComparer = equalityComparer;
        }

        public bool Equals(T first, T second)
        {
            return _equalityComparer(first, second);
        }

        public int GetHashCode(T value)
        {
            return value.GetHashCode();
        }
    }

そして、あなたは例えば次のようなことをすることができます(例:Intersectの場合IEnumerable<T>):

list.Intersect(otherList, new Comparer<T>( (x, y) => x.Property == y.Property));

Comparerクラスはユーティリティプロジェクトに配置し、必要な場所で使用できます。

私は今、サム・サフロンの答えを見るだけです(これは非常に似ています)。

5
Tamas Ionut

他の答えに基づいて、汎用比較器の作成は私が最も気に入ったものでした。しかし、Linq Enumerable.Unionmsdn .Net reference )これは、Equalsオーバーライドを考慮せずにGetHashCodeを直接使用することでした。

そのため、Comparerを次のように実装する必要がありました。

public class Comparer<T> : IEqualityComparer<T>
{
    private readonly Func<T, int> _hashFunction;

    public Comparer(Func<T, int> hashFunction)
    {
        _hashFunction = hashFunction;
    }

    public bool Equals(T first, T second)
    {
        return _hashFunction(first) == _hashFunction(second);
    }

    public int GetHashCode(T value)
    {
        return _hashFunction(value);
    }
}

次のように使用します。

list.Union(otherList, new Comparer<T>( x => x.StringValue.GetHashCode()));

比較対象の情報はint値にマッピングされるため、比較によって誤検知が生じる可能性があることに注意してください。

1
OriolBG

小さいセットの場合、次のことができます。

f3 = f1.Where(x1 => f2.All(x2 => x2.key != x1.key));

大規模なセットの場合、次のようなより効率的な検索が必要になります。

var tmp = new HashSet<string>(f2.Select(f => f.key));
f3 = f1.Where(f => tmp.Add(f.key));

しかし、ここでは、keyTypeIEqualityComparerを実装する必要があります(上記ではstringであると想定しました)。したがって、これは、この状況でラムダを使用することに関するあなたの質問には実際には答えませんが、そうする答えのいくつかよりも少ないコードを使用します。

オプティマイザーに依存して、2番目のソリューションを短縮して以下を行うことができます。

f3 = f1.Where(x1 => (new HashSet<string>(f2.Select(x2 => x2.key))).Add(x1.key));

ただし、同じ速度で実行されるかどうかを確認するテストは実行していません。そして、その1つのライナーは維持するのが賢すぎるかもしれません。

1
mheyman