2012-04-03 1 views
5

서로 다른 유형의 목록 두 개가 있으면 LINQ 쿼리에서 비교할 수 있도록 이러한 유형을 서로 변환하거나 서로 비교할 수 있습니까? 예 : TypeConverter 또는 유사품? 그래서 다른 유사한 질문을 보았습니다. 그러나 문제를 해결하기 위해 서로간에 변환 할 수있는 유형을 만드는 것을 가리키는 것은 없습니다.LINQ : 다른 유형의 콜렉션에서 .Except()를 변환 가능/비교 가능하게 사용 하시겠습니까?

컬렉션 유형 :

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

public class ViewModel 
{ 
    private Data _data; 

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

원하는 사용 : 그것은 내가 데이터의 인스턴스로 뷰 모델의 인스턴스를 비교하는 방법을 알고 있기 때문에 내가 제공 할 수 있어야한다고 논리적 보인다

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 : 당신이 표현하고있는 정서에 동의하지 않습니다. 메커니즘에 대해 걱정하지 않고 의도를보다 명확하게 표현하는 코드를 작성하는 방법이 있습니다. 'for'는 메커니즘을 표현하고 의도를 모호하게 만듭니다. LINQ 기반의 단 하나의 라이너는 종종 (항상 그래야하는 것은 아니지만) 자주 비방하고 의도를 표현하고 메커니즘을 숨 깁니다. 이는 이해하기 쉽고 유지 보수가 쉬운 코드로 이어집니다. – jason

+1

@ Jason, 내가 경박 해지는 동안, 당신과 같은 투사에 던지는 모든 기능은 의도의 가정만을 제공합니다. – Marc

답변

4

가장 좋은 방법은 fViewModelData 매핑 위치를

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

말을 할 수 있도록 ViewModelData에서 프로젝션을 제공하는 것입니다. IEqualityComparer<Data>도 필요합니다.

4

Data에서 ViewModel까지 투영을 제공하는 것은 문제가 있으므로 제이슨 외에 다른 솔루션을 제안한다고 가정합니다.

해시 세트를 사용하는 경우를 제외하고 (올바르게 호출 한 경우), 사용자 자신의 해시 세트를 만들어 비슷한 성능을 얻을 수 있습니다. 또한 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'이라는 비슷한 내부 클래스를 사용하여 동일한 작업을 수행합니다 (디 컴파일 링을 통해 살펴 보았습니다).