2012-04-03 36 views
5

Farklı türde iki liste verildiğinde, bu türlerin birbiriyle karşılaştırılabilir veya birbirine benzetilebilir hale getirilmesi mümkün olabilir (örneğin bir TypeConverter veya benzeri), böylece bir LINQ sorgusu bunları karşılaştırabilir mi? SO ile ilgili başka benzer sorular gördüm, ama problemi çözmek için birbirleri arasında dönüştürülebilir türler yapmaya işaret eden hiçbir şey yok.LINQ: Farklı türlerdeki koleksiyonlarda .Except(), dönüştürülebilir/karşılaştırılabilir hale getirerek kullanın.

Koleksiyon Türleri:

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

public class ViewModel 
{ 
    private Data _data; 

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

İstenilen kullanımı: Ben Verilerin bir örneğine ViewModel bir örneğini karşılaştırmak için biliyorum çünkü ben sağlayabilmelidir gerektiğini mantıklı görünecektir

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'un .Except() gibi sorgular için kullanacağı bazı karşılaştırma mantığı. Mümkün mü?

+1

Zavallı eski 'for' döngü, o bir zamanlar çok kullanışlıdır, ama alas, o asla bir-liner insanı mutlu etmez. – Marc

+2

@Marc: Açıkladığınız duygu ile aynı fikirde değilim. Artık mekanizma hakkında endişe duymadan niyetleri daha net ifade eden kod yazmanın yolları var. “için” mekanizmaları ifade eder ve niyetleri belirler. LINQ-tabanlı tek-astarlılar, genellikle (evet, her zaman değil) daha iyi ifade etmek niyet ve gizlemek mekanizmaları. Bu, anlaşılması ve bakımı daha kolay olan bir kod ortaya çıkarır. – jason

+1

@Jason, flippant iken, sizin gibi bir projeksiyona attığınız herhangi bir işlev, yalnızca bir niyet varsayımı sağlar. – Marc

cevap

4

En iyi bahis fViewModel için Data eşler nerede

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

söyleyebiliriz ki ViewModel için Data bir projeksiyon sağlamaktır. IEqualityComparer<Data>'a da ihtiyacınız olacak.

4

Data'dan ViewModel'a kadar bir projeksiyon sağlamanın sorunlu olduğunu varsayıyorum, bu yüzden Jason'ın yanı sıra başka bir çözüm öneriyorum.

Karma kümesi kullanmama (eğer doğru bir şekilde hatırlamıyorsam), böylece kendi hashset'inizi oluşturarak benzer bir performans elde edebilirsiniz. Ayrıca, IDs eşit olduğunda, Data nesnelerini eşit olarak tanımladığınızı varsayıyorum.

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

Başka bir yerde, bunun yerine bunu yapmak isteyeyim ki bu durumda yöntemde, içinde "oldData" koleksiyonu için başka kullanmak olabilir. Ya veri sınıfına IEquatable<Data> uygulamak veya karma seti için özel bir IEqualityComparer<Data> oluşturun: Eğer kullanırsanız

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

Neden sorunlu olur? 'ViewModel'' Data' adlı bir kurucuya sahiptir! – jason

+0

Performans için ya da felsefi olarak "sorunlu" demek, kodu nasıl yazacağımı anlamıyorum. Performans açısından, 'ViewModel' sınıfı, yapımında bir sürü başka ek yük gerektirebilir. Felsefi olarak, birtakım nesneler yaratmak için garip görünüyor, bu yüzden belirli kriterleri karşılamayan bazı nesneleri seçmek için 'Except' kullanabiliriz. – phoog

+0

'points.All (point => point.IsGoodPoint)' 'true' olarak değerlendirir. – jason

3

bu:

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

Sen içerdiği aynı türe 'verilerini' proje var hedef' ama altınızda kod, bu sınırlama kurtulmak olabilir kullanarak:

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

bazıları nedeniyle bir HashSet kullanımına hafıza verimsiz olduğunu iddia edebilir. Ama aslında, çerçevenin Enumerable.Except yöntemi, 'Set' denen benzer bir iç sınıfla aynı şeyi yapıyor (decompiling ile bir baktım).