Except 메서드
LINQ의 Except() 메서드는 첫 번째 데이터에는 존재하지만 두 번째 데이터에는 없는 요소를 반환합니다. 예를 들어 다음 소스 코드를 봅시다.
int[] intArrayA = { 0, 2, 4, 5 };
int[] intArrayB = { 1, 2, 3, 5 };
int 타입의 배열인 intArrayA와 intArrayB가 존재합니다. 첫 번째 데이터인 intArrayA에는 존재하지만 두 번째 데이터인 intArrayB에 없는 요소는 0, 4입니다.
배열을 그림으로 나타내면 다음과 같습니다.
오버로드된 두 가지 버전
LINQ의 Except() 메서드는 오버로드된 두 가지 버전이 존재합니다.
public static IEnumerable<TSource> Except<TSource>(
this IEnumerable<TSource> first,
IEnumerable<TSource> second);
public static IEnumerable<TSource> Except<TSource>(
this IEnumerable<TSource> first,
IEnumerable<TSource> second,
IEqualityComparer<TSource> comparer);
Except() 메서드는 Enumerable 클래스에서 구현되었으며 IEnumerable 인터페이스의 확장 메서드라는 것을 알 수 있습니다. 두 가지 버전의 차이점은 IEqualityComparer 인터페이스 사용 유무입니다.
IEqualityComparer 인터페이스가 존재한다는 의미는 Except() 메서드를 비교기(Comparer)와 함께 사용할 수 있다는 것입니다. 비교기를 사용하는 방법은 아래 예제에서 확인할 수 있습니다.
예제 1. int 타입의 List
다음 예제는 int 타입의 List에서 Except() 메서드를 사용합니다. 질의 구문에는 except절이 없으므로 질의 구문과 메서드 구문을 조합하여 사용합니다.
class Program
{
static void Main(string[] args)
{
List<int> intArrayA = new List<int>() { 0, 2, 4, 5 };
List<int> intArrayB = new List<int>() { 1, 2, 3, 5 };
// 1. 질의 구문(Query Syntax) + 메서드 구문(Method Syntax)
List<int> linqQueryResult = (from num in intArrayA
select num).Except(intArrayB).ToList();
// 2. 메서드 구문(Method Syntax)
List<int> linqMethodResult = intArrayA.Except(intArrayB).ToList();
Console.WriteLine("질의 구문 + 메서드 구문");
foreach (int num in linqQueryResult)
Console.Write(num + " ");
Console.WriteLine("\n메서드 구문");
foreach (int num in linqMethodResult)
Console.Write(num + " ");
}
}
[실행 결과]
질의 구문 + 메서드 구문
0 4
메서드 구문
0 4
예제 2. string 타입의 List
다음 예제는 string 타입의 List에서 Except() 메서드를 사용합니다. 사용 방법은 int 타입에서 설명한 방법과 동일합니다.
class Program
{
static void Main(string[] args)
{
List<string> strArrayA = new List<string>() { "one", "two", "three" };
List<string> strArrayB = new List<string>() { "One", "Two", "Three" };
// 1. 질의 구문(Query Syntax) + 메서드 구문(Method Syntax)
List<string> linqQueryResult = (from str in strArrayA
select str).Except(strArrayB).ToList();
// 2. 메서드 구문(Method Syntax)
List<string> linqMethodResult = strArrayA.Except(strArrayB).ToList();
Console.WriteLine("질의 구문 + 메서드 구문");
foreach (string str in linqQueryResult)
Console.Write(str + " ");
Console.WriteLine("\n메서드 구문");
foreach (string str in linqMethodResult)
Console.Write(str + " ");
}
}
[실행 결과]
질의 구문 + 메서드 구문
one two three
메서드 구문
one two three
기본 비교자는 문자열을 비교할 때 대소문자를 구분합니다. 따라서 IEqualityComparer를 매개변수로 사용하는 Except() 메서드를 사용하여 대소문자를 구분하지 않도록 합니다.
class Program
{
static void Main(string[] args)
{
List<string> strArrayA = new List<string>() { "one", "two", "three" };
List<string> strArrayB = new List<string>() { "One", "Two", "Three" };
// 1. 질의 구문(Query Syntax) + 메서드 구문(Method Syntax)
List<string> linqQueryResult = (from str in strArrayA
select str)
.Except(strArrayB, StringComparer.OrdinalIgnoreCase)
.ToList();
// 2. 메서드 구문(Method Syntax)
List<string> linqMethodResult = strArrayA
.Except(strArrayB, StringComparer.OrdinalIgnoreCase)
.ToList();
Console.WriteLine("질의 구문 + 메서드 구문");
foreach (string str in linqQueryResult)
Console.Write(str + " ");
Console.WriteLine("\n메서드 구문");
foreach (string str in linqMethodResult)
Console.Write(str + " ");
}
}
예제 3. 사용자 정의 클래스
Except() 메서드는 사용자가 정의한 클래스 타입에서도 사용할 수 있지만 약간 다른 방식으로 동작합니다.
Person이라는 사용자 정의 클래스가 있습니다.
public class Person
{
public string Name { get; set; }
public int Age { get; set; }
public override string ToString()
{
return "Name: " + Name + ", Age: " + Age;
}
}
그리고 Person 타입의 두 개의 List 객체가 존재합니다.
객체 personA 기준으로 Except() 메서드를 호출하면 동일한 요소를 제외하고 다음 두 개의 요소가 반환되어야 합니다.
하지만 다음 예제를 실행하면 예상하는 결과와 달리 personA의 모든 요소가 출력됩니다.
public class Person
{
public string Name { get; set; }
public int Age { get; set; }
public override string ToString()
{
return "Name: " + Name + ", Age: " + Age;
}
}
class Program
{
static void Main(string[] args)
{
List<Person> personA = new List<Person>
{
new Person{Name ="Bob", Age = 20},
new Person{Name ="Tom", Age = 25},
new Person{Name ="Sam", Age = 30}
};
List<Person> personB = new List<Person>
{
new Person{Name ="Ella", Age = 15},
new Person{Name ="Bob", Age = 20}
};
// 1. 질의 구문(Query Syntax) + 메서드 구문(Method Syntax)
List<Person> linqQueryResult = (from person in personA
select person)
.Except(personB)
.ToList();
// 2. 메서드 구문(Method Syntax)
List<Person> linqMethodResult = personA.Except(personB).ToList();
Console.WriteLine("질의 구문 + 메서드 구문");
foreach (Person person in linqQueryResult)
Console.WriteLine(person.ToString());
Console.WriteLine("\n메서드 구문");
foreach (Person person in linqMethodResult)
Console.WriteLine(person.ToString());
}
}
[실행 결과]
질의 구문 + 메서드 구문
Name: Bob, Age: 20
Name: Tom, Age: 25
Name: Sam, Age: 30
메서드 구문
Name: Bob, Age: 20
Name: Tom, Age: 25
Name: Sam, Age: 30
위 문제를 해결하기 위해 익명 타입과 비교자를 사용하는 방식을 알아봅시다.
예제 4. 익명 타입 사용
익명 타입을 사용하는 방법은 Select절을 사용하여 객체의 모든 요소를 익명 타입으로 생성합니다.
public class Person
{
public string Name { get; set; }
public int Age { get; set; }
}
class Program
{
static void Main(string[] args)
{
List<Person> personA = new List<Person>
{
new Person{Name ="Bob", Age = 20},
new Person{Name ="Tom", Age = 25},
new Person{Name ="Sam", Age = 30}
};
List<Person> personB = new List<Person>
{
new Person{Name ="Ella", Age = 15},
new Person{Name ="Bob", Age = 20}
};
// 1. 질의 구문(Query Syntax) + 메서드 구문(Method Syntax)
var linqQueryResult = (from person in personA
select new { person.Name, person.Age })
.Except(personB.Select(item => new { item.Name, item.Age }));
// 2. 메서드 구문(Method Syntax)
var linqMethodResult = personA.Select(item => new { item.Name, item.Age })
.Except(personB.Select(item => new { item.Name, item.Age }));
Console.WriteLine("질의 구문 + 메서드 구문");
foreach (var person in linqQueryResult)
Console.WriteLine(person.ToString());
Console.WriteLine("\n메서드 구문");
foreach (var person in linqMethodResult)
Console.WriteLine(person.ToString());
}
}
[실행 결과]
질의 구문 + 메서드 구문
{ Name = Tom, Age = 25 }
{ Name = Sam, Age = 30 }
메서드 구문
{ Name = Tom, Age = 25 }
{ Name = Sam, Age = 30 }
예제 5. IEqualityComparer 인터페이스 구현
IEqualityComparer 인터페이스 구현 클래스가 필요합니다. 따라서 IEqualityComparer 인터페이스를 구현하는 PersonComparer이라는 클래스를 추가합니다.
public class Person
{
public string Name { get; set; }
public int Age { get; set; }
public override string ToString()
{
return "Name: " + Name + ", Age: " + Age;
}
}
public class PersonComparer : IEqualityComparer<Person>
{
public bool Equals(Person x, Person y)
{
if (object.ReferenceEquals(x, y))
{
return true;
}
if (object.ReferenceEquals(x, null) || object.ReferenceEquals(y, null))
{
return false;
}
return x.Name == y.Name && x.Age == y.Age;
}
public int GetHashCode(Person obj)
{
if (obj == null)
{
return 0;
}
int NameHashCode = obj.Name == null ? 0 : obj.Name.GetHashCode();
int AgeHashCode = obj.Age.GetHashCode();
return NameHashCode ^ AgeHashCode;
}
}
class Program
{
static void Main(string[] args)
{
List<Person> personA = new List<Person>
{
new Person{Name ="Bob", Age = 20},
new Person{Name ="Tom", Age = 25},
new Person{Name ="Sam", Age = 30}
};
List<Person> personB = new List<Person>
{
new Person{Name ="Ella", Age = 15},
new Person{Name ="Bob", Age = 20}
};
PersonComparer personComparer = new PersonComparer();
// 1. 질의 구문(Query Syntax) + 메서드 구문(Method Syntax)
List<Person> linqQueryResult = (from person in personA
select person)
.Except(personB, personComparer)
.ToList();
// 2. 메서드 구문(Method Syntax)
List<Person> linqMethodResult = personA.Except(personB, personComparer).ToList();
Console.WriteLine("질의 구문 + 메서드 구문");
foreach (Person person in linqQueryResult)
Console.WriteLine(person.ToString());
Console.WriteLine("\n메서드 구문");
foreach (Person person in linqMethodResult)
Console.WriteLine(person.ToString());
}
}
[실행 결과]
질의 구문 + 메서드 구문
Name: Tom, Age: 25
Name: Sam, Age: 30
메서드 구문
Name: Tom, Age: 25
Name: Sam, Age: 30
'C# > LINQ' 카테고리의 다른 글
[C#]LINQ 합집합 구하는 방법 - Union 메서드 (0) | 2022.07.17 |
---|---|
[C#]LINQ 교집합 구하는 방법 - Intersect 메서드 (0) | 2022.07.14 |
[C#]LINQ 특정 타입 추출하는 방법 - OfType 메서드 (0) | 2022.07.12 |
[C#]LINQ 인덱스 가져오는 방법 (0) | 2022.07.12 |
[C#]LINQ 데이터 필터링 방법 - Where절 (0) | 2022.07.10 |
댓글