2012-04-03 8 views
5

Ist es bei zwei Listen verschiedener Typen möglich, diese Typen konvertierbar oder miteinander vergleichbar zu machen (z. B. mit einem TypeConverter oder ähnlichem), damit eine LINQ-Abfrage sie vergleichen kann? Ich habe andere ähnliche Fragen zu SO gesehen, aber nichts, was darauf hindeutet, dass die Typen untereinander umwandelbar sind, um das Problem zu lösen. TypenLINQ: Verwenden Sie .Except() für Sammlungen unterschiedlicher Typen, indem Sie sie konvertierbar/vergleichbar machen?

Collection:

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

public class ViewModel 
{ 
    private Data _data; 

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

Wunsch Nutzung:

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

     // ... 
    } 

Es wäre logisch, dass, da ich weiß, wie eine Instanz von Viewmodel zu einer Instanz von Daten zu vergleichen, soll ich in der Lage sein zu liefern eine Vergleichslogik, die LINQ dann für Abfragen wie .Except() verwenden würde. Ist das möglich?

+1

Schlechte alte 'For'-Schleife, er war einmal so nützlich, aber leider macht er die One-Liner-Leute nie glücklich. – Marc

+2

@Marc: Ich stimme nicht mit der Stimmung, die Sie ausdrücken. Wir haben jetzt Möglichkeiten, Code zu schreiben, der die Absicht deutlicher zum Ausdruck bringt, ohne sich Gedanken über den Mechanismus machen zu müssen. 'for' drückt Mechanismen aus und verdeckt die Absicht. Die LINQ-basierten Einzeiler, die Sie häufig (ja, nicht immer) entschlüsseln, drücken Absicht und verbergen Mechanismen. Dies führt zu Code, der einfacher zu verstehen und zu pflegen ist. – jason

+1

@Jason, während ich leichtfertig war, sind alle Funktionen, die Sie in eine Projektion wie Sie werfen, nur eine Annahme der Absicht. – Marc

Antwort

4

Ihre beste Wette ist eine Projektion Data-ViewModel bereitzustellen, so dass Sie

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

sagen kann, wo fData-ViewModel abbildet. Sie werden auch eine IEqualityComparer<Data> benötigen.

4

Ich gehe davon aus, dass die Bereitstellung einer Projektion von bis ViewModel problematisch ist, also biete ich neben Jason eine weitere Lösung an.

Außer verwendet einen Hash-Satz (wenn ich mich richtig erinnere), so können Sie ähnliche Leistung erzielen, indem Sie Ihren eigenen Hash-Satz erstellen. Ich nehme auch an, dass Sie Data Objekte als gleich identifizieren, wenn ihre IDs gleich sind.

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

Sie könnten eine andere Verwendung für eine Sammlung von „olddata“ haben an anderer Stelle in dem Verfahren, in welchem ​​Fall, Sie wollen würden, dies zu tun, statt. Entweder implementieren IEquatable<Data> auf Ihre Datenklasse, oder erstellen Sie eine benutzerdefinierte IEqualityComparer<Data> für den Hash-Set:

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

Warum wäre es problematisch? 'ViewModel' hat einen Konstruktor der' Data'! – jason

+0

Ich meine "problematisch" für die Leistung, oder philosophisch, nicht in Bezug darauf herauszufinden, wie man den Code schreibt. In Bezug auf die Leistung benötigt die Klasse "ViewModel" möglicherweise viel zusätzlichen Overhead in ihrer Konstruktion. Philosophisch erscheint es seltsam, eine Reihe von Objekten zu erzeugen, nur um einige Objekte, die bestimmte Kriterien nicht erfüllen, mit "Außer" zu selektieren. – phoog

+0

'points.All (point => point.IsGoodPoint)' evaluiert zu 'true'. – jason

3

Wenn Sie diese:

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

Sie haben zu projizieren 'data' zu gleichen Typs enthalten in " Ziel‘, aber mit dem Code unten Sie könnte von dieser Einschränkung befreien:

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

Einige argumentieren, dass das Gedächtnis ineffizient aufgrund der Verwendung eines HashSet. Aber die Enumerable.Except-Methode des Frameworks tut dasselbe mit einer ähnlichen internen Klasse namens 'Set' (ich habe es mir beim Dekompilieren angesehen).