2012-04-03 10 views
5

Dato due elenchi di tipi diversi, è possibile rendere questi tipi convertibili o paragonabili tra loro (ad esempio con un TypeConverter o simile) in modo che una query LINQ possa confrontarli? Ho visto altre domande simili su SO, ma nulla che punti a rendere i tipi convertibili l'uno con l'altro per risolvere il problema.LINQ: Usa. Eccetto() su raccolte di tipi diversi rendendoli convertibili/comparabili?

Tipi Collection:

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

public class ViewModel 
{ 
    private Data _data; 

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

utilizzo desiderata:

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

     // ... 
    } 

Sembrerebbe logico che dal momento che so come confrontare un'istanza di ViewModel a un'istanza di dati dovrei essere in grado di fornire alcune logiche di confronto che LINQ avrebbe quindi utilizzato per query come .Except(). È possibile?

+1

Povero vecchio ciclo "for', una volta era così utile ma, ahimè, non rende mai felici le persone con un solo liner. – Marc

+2

@Marc: Non sono d'accordo con il sentimento che stai esprimendo. Abbiamo modi di scrivere il codice ora che esprime più chiaramente l'intento senza preoccuparsi del meccanismo. 'for' esprime meccanismi e oscura l'intento. Gli one-liner basati su LINQ che stai denigrando spesso (sì, non sempre) esprimono meglio l'intenzione e nascondono i meccanismi. Questo porta a un codice che è più facile da capire e mantenere. – jason

+1

@Jason, mentre ero impertinente, tutte le funzioni che si proiettano in una proiezione come se fosse solo una supposizione di intenti. – Marc

risposta

4

La cosa migliore è quello di fornire una proiezione Data-ViewModel in modo che si può dire

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

dove f mappe Data-ViewModel. Avrai anche bisogno di un IEqualityComparer<Data>.

4

Suppongo che fornire una proiezione da Data a ViewModel sia problematico, quindi offro un'altra soluzione oltre a quella di Jason.

Tranne che utilizza un set di hash (se ricordo correttamente), in modo da poter ottenere prestazioni simili creando il proprio hashset. Suppongo anche che tu stia identificando gli oggetti Data come uguali quando il loro IDs è uguale.

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

Si potrebbe avere un altro uso per una collezione di "OldData" altrove nel metodo, nel qual caso, si vorrebbe fare questo, invece. O implementare IEquatable<Data> sulla classe di dati, o creare un personalizzato IEqualityComparer<Data> per il set di hash:

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

Perché sarebbe problematico? 'ViewModel' ha un costruttore che prende' Data'! – jason

+0

Intendo "problematico" per le prestazioni, o filosoficamente, non in termini di capire come scrivere il codice. Per quanto riguarda le prestazioni, la classe 'ViewModel' potrebbe richiedere un sacco di altro overhead nella sua costruzione. Dal punto di vista filosofico, sembra strano creare un gruppo di oggetti solo per usare 'Eccetto' per selezionare alcuni oggetti che non soddisfano determinati criteri. – phoog

+0

'points.All (point => point.IsGoodPoint)' passa a 'true'. – jason

3

Se si utilizza questo:

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

Dovete proiettare 'dati' per lo stesso tipo di contenuto in ' destinazione', ma usando il codice seguente si potrebbe sbarazzarsi di questa limitazione:

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

Alcuni potrebbero obiettare che è la memoria inefficiente a causa dell'uso di un HashSet. Ma in realtà il metodo Enumerable.Except del framework sta facendo lo stesso con una classe interna simile chiamata 'Set' (ho dato un'occhiata decompilando).

Problemi correlati