Distinct 메서드
LINQ의 Distinct() 메서드는 단일 데이터 타입에서 중복되는 요소를 제거하고 고유한 요소를 반환합니다.
단일 데이터 타입
사용자 정의 클래스가 아닌 int, string, boolean 등 기본 타입을 의미합니다.
Distinct() 메서드는 다음 코드처럼 오버로드된 두 가지 버전이 존재합니다.
public static IEnumerable<TSource> Distinct<TSource>(this IEnumerable<TSource> source);
public static IEnumerable<TSource> Distinct<TSource>(
this IEnumerable<TSource> source,
IEqualityComparer<TSource> comparer
);
두 가지 버전의 차이는 IEqualityComparer를 사용 유무입니다. IEqualityComparer는 어떤 기준으로 값을 비교할 것인지 비교하는 기준을 명시하는 비교자(Comparer)입니다.
예제 1. 기본 데이터 타입
다음 예제는 int 타입의 값으로 구성된 List에서 중복되는 값을 제거하고 고유한 값을 반환합니다.
class Program
{
static void Main(string[] args)
{
List<int> li = new List<int>()
{
1, 1, 2, 3, 2, 3, 4, 4, 5, 4
};
// 1. Query 구문
var queryResult = (from num in li
select num).Distinct();
// 2. Method 구문
var methodResult = li.Distinct();
Console.WriteLine("Query 구문 결과");
foreach (var num in queryResult)
Console.Write(num + " ");
Console.WriteLine("\nMethod 구문 결과");
foreach (var num in methodResult)
Console.Write(num + " ");
}
}
[실행 결과]
Query 구문 결과
1 2 3 4 5
Method 구문 결과
1 2 3 4 5
질의 구문은 질의 결과에서 Distinct() 메서드를 호출하고 메서드 구문은 컬렉션 객체에서 Distinct() 메서드를 호출합니다.
다음 예제는 string 타입의 값으로 구성된 List에서 중복되는 값을 제거하고 고유한 값을 반환합니다.
class Program
{
static void Main(string[] args)
{
List<String> li = new List<String>()
{
"AA", "AA", "BB", "BB", "aa", "bb", "C", "C"
};
// 1. Query 구문
var queryResult = (from str in li
select str).Distinct();
// 2. Method 구문
var methodResult = li.Distinct();
Console.WriteLine("Query 구문 결과");
foreach (var str in queryResult)
Console.Write(str + " ");
Console.WriteLine("\nMethod 구문 결과");
foreach (var str in methodResult)
Console.Write(str + " ");
}
}
[실행 결과]
Query 구문 결과
AA BB aa bb C
Method 구문 결과
AA BB aa bb C
중복되는 값은 제거되었지만, 기본 비교자가 대소문자를 구분합니다. 대소문자를 구분하지 않고 중복되는 값을 제거하려면 IEqualityComparer를 인수를 사용하는 Distinct() 메서드를 사용합니다.
다음 예제는 Distinct() 메서드에서 StringComparer 클래스의 OrdinalIgnoreCase 프로퍼티를 전달합니다.
class Program
{
static void Main(string[] args)
{
List<String> li = new List<String>()
{
"AA", "AA", "BB", "BB", "aa", "bb", "C", "C"
};
// 1. Query 구문
var queryResult = (from str in li
select str).Distinct(StringComparer.OrdinalIgnoreCase);
// 2. Method 구문
var methodResult = li.Distinct(StringComparer.OrdinalIgnoreCase);
Console.WriteLine("Query 구문 결과");
foreach (var str in queryResult)
Console.Write(str + " ");
Console.WriteLine("\nMethod 구문 결과");
foreach (var str in methodResult)
Console.Write(str + " ");
}
}
[실행 결과]
Query 구문 결과
AA BB C
Method 구문 결과
AA BB C
예제 2. 사용자 정의 클래스
사용자 정의 클래스인 Person으로 구성된 List에서 이름(name)이 중복되지 않은 값을 반환해보겠습니다.
class Person
{
public string name;
public int age;
}
이름만 추출할 것이므로 질의 구문의 select 키워드와 메서드 구문의 Select() 메서드에는 이름(name)만 가져오도록 합니다.
class Program
{
static void Main(string[] args)
{
List<Person> person = new List<Person>
{
new Person() { name = "Bob", age = 20},
new Person() { name = "Bob", age = 21},
new Person() { name = "Nick", age = 22},
new Person() { name = "Nick", age = 23},
new Person() { name = "John", age = 24},
new Person() { name = "John", age = 25},
new Person() { name = "Tim", age = 26},
new Person() { name = "Tim", age = 27},
};
// 1. Query 구문
var queryResult = (from obj in person
select obj.name).Distinct();
// 2. Method 구문
var methodResult = person.Select(obj => obj.name).Distinct();
Console.WriteLine("Query 구문 결과");
foreach (var str in queryResult)
Console.Write(str + " ");
Console.WriteLine("\nMethod 구문 결과");
foreach (var str in methodResult)
Console.Write(str + " ");
}
}
[실행 결과]
Query 구문 결과
Bob Nick John Tim
Method 구문 결과
Bob Nick John Tim
이제 다음과 같이 name, age 프로퍼티의 값이 동일하고 모든 프로퍼티를 추출 후 Distinct() 메서드를 호출하면 어떻게 되는지 확인해봅시다.
List<Person> person = new List<Person>
{
new Person() { name = "Bob", age = 20},
new Person() { name = "Bob", age = 20},
new Person() { name = "Bob", age = 20},
new Person() { name = "Bob", age = 20},
};
name, age 프로퍼티의 값이 동일한 객체가 4개 존재하므로 단 하나의 객체만 반환되어야 합니다.
class Program
{
static void Main(string[] args)
{
List<Person> person = new List<Person>
{
new Person() { name = "Bob", age = 20},
new Person() { name = "Bob", age = 20},
new Person() { name = "Bob", age = 20},
new Person() { name = "Bob", age = 20},
};
// 1. Query 구문
var queryResult = (from obj in person
select obj).Distinct();
// 2. Method 구문
var methodResult = person.Distinct();
Console.WriteLine("Query 구문 결과");
foreach (var obj in queryResult)
Console.WriteLine("name: " + obj.name + " / age: " + obj.age);
Console.WriteLine("\nMethod 구문 결과");
foreach (var obj in methodResult)
Console.WriteLine("name: " + obj.name + " / age: " + obj.age);
}
}
[실행 결과]
Query 구문 결과
name: Bob / age: 20
name: Bob / age: 20
name: Bob / age: 20
name: Bob / age: 20
Method 구문 결과
name: Bob / age: 20
name: Bob / age: 20
name: Bob / age: 20
name: Bob / age: 20
예상했던 결과와 다르게 모든 객체가 반환되었습니다. 기본 비교자는 객체의 프로퍼티 값을 비교하지 않으므로 모든 객체가 반환됩니다.
예제 3. 사용자 정의 클래스 - 비교자
사용자 정의 클래스로 구성된 컬렉션에서 중복되는 요소를 제거하기 위해 다음 세 가지 방법을 사용할 수 있습니다.
- Equals() 및 GetHashCode() 메서드 재정의
- 익명 타입 사용
- 사용자 정의 클래스에서 IEquatable<T> 인터페이스 구현
방법 1. Equals() 및 GetHashCode() 메서드 재정의
Person 클래스 내부에서 Equals() 및 GetHasCode() 메서드를 재정의합니다.
class Person
{
public string name;
public int age;
public override bool Equals(object obj)
{
return this.name == ((Person)obj).name && this.age == ((Person)obj).age;
}
public override int GetHashCode()
{
return name.GetHashCode() ^ age.GetHashCode();
}
}
방법 2. 익명 타입 사용
// 1. Query 구문
var queryResult = (from obj in person
select obj)
.Select(obj => new { obj.name, obj.age })
.Distinct();
// 2. Method 구문
var methodResult = person.Select(obj => new { obj.name, obj.age }).Distinct();
방법 3. 사용자 정의 클래스에서 IEquatable<T> 인터페이스 구현
사용자 정의 클래스에서 IEquatable 인터페이스의 Equals() 및 GetHashCode() 메서드를 재정의 합니다.
class Person : IEquatable<Person>
{
public string name;
public int age;
public bool Equals(Person other)
{
if (object.ReferenceEquals(other, null))
{
return false;
}
if (object.ReferenceEquals(this, other))
{
return true;
}
return this.name.Equals(other.name) && this.age.Equals(other.age);
}
public override int GetHashCode()
{
return name.GetHashCode() ^ age.GetHashCode();
}
}
정리
- LINQ에서 중복 요소를 제거하고 싶은 경우 Distinct() 메서드를 사용합니다.
- Distinct() 메서드는 질의 구문과 메서드 구문에서 사용할 수 있습니다.
- 사용자 정의 클래스로 구성된 컬렉션에서 Distinct() 메서드를 사용하는 경우 비교자를 구현합니다.
'C# > LINQ' 카테고리의 다른 글
[C#]LINQ Select절 (0) | 2022.07.10 |
---|---|
[C#]LINQ 확장 메서드 (0) | 2022.07.10 |
[C#]LINQ와 IEnumerable, IQueryable 인터페이스 (0) | 2022.07.10 |
[C#]LINQ 질의 구문과 메서드 구문 (0) | 2022.07.10 |
[C#]링크(LINQ)란? (1) | 2022.07.09 |
댓글