2012-04-03 8 views
5

異なるタイプの2つのリストが与えられている場合、LINQクエリで比較できるように、これらのタイプを(たとえばTypeConverterなどで)互いの間で変換または匹敵するようにすることは可能ですか?私はそれについて他の同様の質問を見たことがありますが、問題を解決するためにタイプを相互に変換することを指摘するものは何もありません。LINQ:異なるタイプのコレクションで.Except()を使用すると、コンバチブル/比較可能になります。

コレクション型:

public class Data 
{ 
    public int ID { get; set; } 
} 

public class ViewModel 
{ 
    private Data _data; 

    public ViewModel(Data data) 
    { 
     _data = data; 
    } 
} 

理想の用法:それは私がデータのインスタンスにViewModelにのインスタンスを比較する方法を知っているので、私は提供できなければならないことを論理的と思われる

public void DoMerge(ObservableCollection<ViewModel> destination, IEnumerable<Data> data) 
    { 
     // 1. Find items in data that don't already exist in destination 
     var newData = destination.Except(data); 

     // ... 
    } 

LINQが.Except()のようなクエリに使用する比較ロジック。これは可能ですか?

+1

貧弱な古い 'for'ループは、かつてはとても有用でしたが、残念なことに、彼は一人の人間を幸せにすることはありません。 – Marc

+2

@Marc:あなたが表現している感情には同意しません。私たちはコードを書く方法を持っていますが、そのメカニズムについて心配することなく、意図をより明確に表現することができます。 'のために'はメカニズムを表現し、意図を隠す。あなたがしばしば拒否しているLINQベースの1ライナーは、(いつもとは限りませんが)意図をよりよく表現し、メカニズムを隠すことができます。これにより、理解しやすく保守しやすいコードになります。 – jason

+1

@ジェイソン、私は軽度であったが、あなたのような投射に投げる機能は、意図の仮定を提供するだけである。 – Marc

答えて

4

あなたの最善の策は、あなたがfViewModelからDataをマップ

var newData = destination.Except(data.Select(x => f(x))); 

を言うことができるようにDataからViewModelへの投影を提供することです。 IEqualityComparer<Data>も必要です。

4

私は、DataからViewModelへの投影を提供することは問題であると考えています。したがって、私はJason'sに加えて別のソリューションを提供しています。

ハッシュセットを使用する以外は(正しく呼び出すと)、独自のハッシュセットを作成して同様のパフォーマンスを得ることができます。私はまた、IDsが等しいときにDataオブジェクトを等しいと特定していると仮定しています。

var oldIDs = new HashSet<int>(data.Select(d => d.ID)); 
var newData = destination.Where(vm => !oldIDs.Contains(vm.Data.ID)); 

あなたが他の場所で、あなたの代わりにこれをしたいと思われる場合には方法では、「OLDDATA」のコレクションのための別の用途を持っているかもしれません。どちらかあなたのデータクラスにIEquatable<Data>を実装する、またはハッシュセットのカスタムIEqualityComparer<Data>作成:あなたが使用している場合

var oldData = new HashSet<Data>(data); 
//or: var oldData = new HashSet<Data>(data, new DataEqualityComparer()); 
var newData = destination.Where(vm => !oldData.Contains(vm.Data)); 
+0

なぜそれが問題になるのでしょうか? 'ViewModel'は' Data'を取るコンストラクタを持っています! – jason

+0

私はパフォーマンスのために「問題がある」、または哲学的に、コードを書く方法を考え出すという点ではありません。パフォーマンス面では、 'ViewModel'クラスは構築に他の多くのオーバーヘッドを必要とするかもしれません。哲学的には、いくつかのオブジェクトを作成するのは奇妙に思えるので、特定の基準を満たさないオブジェクトを選択するには、「Except」を使用できます。 – phoog

+0

'points.All(point => point.IsGoodPoint)'は 'true'に評価されます。 – jason

3

をこの:

var newData = destination.Except(data.Select(x => f(x))); 

あなたは "に含まれている同じタイプの「データ」を投影する必要があり先」が、あなたの下のコードを使用したが、この制限を取り除くことができます:

//Here is how you can compare two different sets. 
class A { public string Bar { get; set; } } 
class B { public string Foo { get; set; } } 

IEnumerable<A> setOfA = new A[] { /*...*/ }; 
IEnumerable<B> setOfB = new B[] { /*...*/ }; 
var subSetOfA1 = setOfA.Except(setOfB, a => a.Bar, b => b.Foo); 

//alternatively you can do it with a custom EqualityComparer, if your not case sensitive for instance. 
var subSetOfA2 = setOfA.Except(setOfB, a => a.Bar, b => b.Foo, StringComparer.OrdinalIgnoreCase); 

//Here is the extension class definition allowing you to use the code above 
public static class IEnumerableExtension 
{ 
    public static IEnumerable<TFirst> Except<TFirst, TSecond, TCompared>(
     this IEnumerable<TFirst> first, 
     IEnumerable<TSecond> second, 
     Func<TFirst, TCompared> firstSelect, 
     Func<TSecond, TCompared> secondSelect) 
    { 
     return Except(first, second, firstSelect, secondSelect, EqualityComparer<TCompared>.Default); 
    } 

    public static IEnumerable<TFirst> Except<TFirst, TSecond, TCompared>(
     this IEnumerable<TFirst> first, 
     IEnumerable<TSecond> second, 
     Func<TFirst, TCompared> firstSelect, 
     Func<TSecond, TCompared> secondSelect, 
     IEqualityComparer<TCompared> comparer) 
    { 
     if (first == null) 
      throw new ArgumentNullException("first"); 
     if (second == null) 
      throw new ArgumentNullException("second"); 
     return ExceptIterator<TFirst, TSecond, TCompared>(first, second, firstSelect, secondSelect, comparer); 
    } 

    private static IEnumerable<TFirst> ExceptIterator<TFirst, TSecond, TCompared>(
     IEnumerable<TFirst> first, 
     IEnumerable<TSecond> second, 
     Func<TFirst, TCompared> firstSelect, 
     Func<TSecond, TCompared> secondSelect, 
     IEqualityComparer<TCompared> comparer) 
    { 
     HashSet<TCompared> set = new HashSet<TCompared>(second.Select(secondSelect), comparer); 
     foreach (TFirst tSource1 in first) 
      if (set.Add(firstSelect(tSource1))) 
       yield return tSource1; 
    } 
} 

一部は、それが原因のHashSetの使用メモリは非効率的だと主張します。しかし、実際には、フレームワークのEnumerable.Exceptメソッドは、同様の内部クラス 'Set'(私は逆コンパイルで見た)と同じことをやっています。

関連する問題