05
28

 

앞서 단일 객체의 같음에 대해서 테스트해봤다.

[🌍 C# Study/C# 케이스 스터디] - C# 클래스, 구조체, 레코드의 같음

 

하지만 위에 글에서는 단일 객체끼리의 같음만을 비교한다. 

List, Array 등 여러 요소들이 담겨있는 시퀀스 컨테이너들의 같음은 어떻게 판단할 것인가?

개별 요소들의 순서와 값이 모두 일치해야 같다고 할 수 있을 것이다.

 

이를 위해 C#라이브러리에서는 Enumerable.SequenceEqual 메서드를 제공한다.

 

MSDN : Enumerable.SequenceEqual 메서드
https://learn.microsoft.com/ko-kr/dotnet/api/system.linq.enumerable.sequenceequal?view=net-8.0

 

오버로딩으로 두 가지 버전을 제공하며, 비교할 시퀀스들을 제공하고 IEqualityComparer를 직접 넘겨줄 수도 있다.

두 시퀀스의 길이가 같고, 내용도 같다면 true를 반환한다. 단. 둘 중 하나가 null일 경우 예외를 반환한다.

둘 중 하나 이상이 null인 경우의 처리는 별도로 구현해야 된다는 이야기이다. 둘 다 null일 경우 같다고 할 수 있을까? 이는 구현 의도에 따라 다르게 처리해야 될 것이다.

SequenceEqual<TSource>(IEnumerable<TSource>, IEnumerable<TSource>);
SequenceEqual<TSource>(IEnumerable<TSource>, IEnumerable<TSource>, IEqualityComparer<TSource>);

 

 

1. 비교 테스트 : 별도 구현 없이 바로 비교

다시 데이터 클래스를 준비하자.

public class MyData 
{
    public int num1;
    public int num2;
    public MyData(int num1, int num2)
    {
        this.num1 = num1;
        this.num2 = num2;
    }
}

 

테스트는 다음과 같다.

public class EqualTest : MonoBehaviour
{
    [Button("테스트2번")]
    public void Test2()
    {
        var listA = new List<MyData>()
        {
            new MyData(1, 2),
            new MyData(2, 3),
        };

        var listB = new List<MyData>()
        {
            new MyData(1, 2),
            new MyData(2, 3),
        };

        Debug.Log("---------테스트------------");
        Debug.Log("== : " + (listA == listB));
        Debug.Log("objectEquals : " + (listA.Equals(listB)));
        Debug.Log("Enumerable.Equals : " + Enumerable.Equals(listA, listB));
        Debug.Log("Enumerable.SequenceEqual : " + Enumerable.SequenceEqual(listA, listB));
    }
}

 

== 와 object.Equals, Enumerable.Equals는 두 리스트의 레퍼런스를 비교하니 false를 반환한다.

Enumerable.SequenceEqual 은 요소의 길이와 순서, 대응되는 요소가 같아야 하는데 대응되는 요소를 비교할 때 레퍼런스 비교를 하게 되니 false를 반환한다.

 

때문에 record 타입이나 struct 타입으로 실행해 보면 다음과 같이 Enumerable.SequenceEqual이 true를 반환하게 된다.

 

 

2. IEquatable<T>를 구현한 상태에서 비교

그렇다면 클래스일 때도 각 요소를 구조 같음으로 비교할 수 있게 IEquatable<T>를 구현해보면 어떨까?

public class MyData : IEquatable<MyData>
{
    public int num1;
    public int num2;
    public MyData(int num1, int num2)
    {
        this.num1 = num1;
        this.num2 = num2;
    }

    public bool Equals(MyData other)
    {
        if (other == null) return false;
        return (this.num1 == other.num1) && (this.num2 == other.num2);
    }
}

다음의 결과를 얻을 수 있다.

 

IEquatable<T>를 구현하면 직접 정의한 비교를 통해 같음을 체크할 수 있다!

 

 

이번에는 null인 경우 처리가 잘 되었는지 테스트해보자.

var listA = new List<MyData>()
{
    null,
    new MyData(2, 3),
};

var listB = new List<MyData>()
{
    null,
    new MyData(2, 3),
};

 

1. 한쪽이 null인 경우 null 처리했던 Equals내부의 첫 번째 줄에서 바로 false를 반환한다.

2. 양쪽이 null 인 경우 object.Euqals의 기본 정의에 의해 true가 반환된다. 

 

때문에 Enumerable.SequenceEqual 비교를 할 때 null이 섞여있어도 오류가 나지 않게 된다.

 

 

3. string의 비교

마지막으로 데이터 객체 대신 레퍼런스 타입이지만 특수하게 비교되는 문자열로 테스트해 보자.

public void Test2()
{
    var listA = new List<string>()
    {
        null,
        "abcd",
    };

    var listB = new List<string>()
    {
        null,
        "abcd",
    };

    Debug.Log("---------테스트------------");
    Debug.Log("Enumerable.SequenceEqual : " + Enumerable.SequenceEqual(listA, listB));
}

문자열은 문자가 같다면 같은 레퍼런스를 사용하게 되니 true를 반환한다.

 

그렇다면 대소문자만 다른 경우는 어떨까?

var listA = new List<string>()
{
    null,
    "abcd",
};

var listB = new List<string>()
{
    null,
    "ABcd",
};

일반 문자열을 ==나 Equals로 비교할 때와 마찬가지로 false를 반환한다.

 

string에서는 문자열의 대소문자를 무시하기 위해 stringComparison을 사용하곤 했다.

bool isSame = "abcd".Equals("ABcd", StringComparison.OrdinalIgnoreCase);

 

Sequence.Equal의 두 번째 오버로딩에서는 별도의 Comparer를 넘길 수 있게 구현되어 있다.

따라서 다음과 같이 StringComparer를 사용하면 대소문자만 다른 경우 같은 문자열로 취급할 수 있다.

Enumerable.SequenceEqual(listA, listB, StringComparer.OrdinalIgnoreCase);

 

즉, 객체 안에 Equals 등을 구현하기 힘들거나 클래스의 간결화를 위해 비교로직을 분리하고 싶다면 별도의 커스텀 Comparer 클래스를 정의해서 활용하면 된다.

 

 

결론

 Enumerable.SeuqneceEqual을 알게 된 후로는 꽤나 즐겨 사용하고 있다. 하지만 데이터 객체의 멤버변수가 추가될 때마다 Euqals에서도 변경을 반영해줘야 하는데 이로 인한 휴먼에러가 자주 발생하는 편이다.

 

 때문에 Equals를 오버라이딩한다면 언제나 주의 또 주의해야 한다!

 

 

 

'🌍 C# Study > C# 케이스 스터디' 카테고리의 다른 글

C# 클래스, 구조체, 레코드의 같음  (0) 2024.05.27
소수의 판별  (0) 2023.01.25
약수의 개수  (0) 2023.01.25
C# LINQ GroupBy, Group by into  (0) 2023.01.07
C# 10진수 2진수 변환 Convert.ToString()  (0) 2022.12.24
COMMENT