첫번째 주제는 문자열 비교에 대한 부분입니다.
Java 프로그래머라면 초급때부터 주구장창 들어온 격언(?)이 하나 있을겁니다.
유명한 Josuha Bloch 의 Effective Java 책에서도 하나의 아이템으로 나오는 부분으로서, 문자열 비교시에 == 를 쓰면 안되고 .equals()를 써야 한다는 것이죠.
원체 많이 알려진 내용이라 간단하게만 언급하자면 == 연산자는 객체 자체의 동일성을 비교하는 것으로서 쉽게 말하면 메모리상에서 주어진 문자열이 위치하는 영역이 동일한지를 보는 것이고 equals()는 위치는 상관없고 실제 문자열의 각 문자 하나하나가 모두 동일한지를 보는 것이기 때문에 equals() 로는 동일하다고 나오더라도 == 로는 다르다고 판정되는 경우가 있다는 것이죠.
먼저 Java의 예를 봅시다.
아래와 같은 코드를 실행했을 때 결과가 어떻게 될까요?
public void static main(String[] args) {
String a = "11";
String b = "11";
String c = "1111".substring(0, 2);
System.out.println(a.equals(b) + "\t" + a.equals(c));
System.out.println((a == b) + "\t" + (a == c));
}
일단, PC 별로 결과가 다를 가능성이 없다고는 못하겠는데 아마도 대부분의 경우 다음과 같이 나올 겁니다.
위에 적은 대로 equals는 값을 비교하기 때문에 실제 값이 모두 "11" 이므로 당연히 모두 true가 나오지만 ==의 비교의 경우는 a 와 b는 같다고 나오지만 a와 c는 다르다고 나온다는게 문제인거죠.
== 연산의 경우 a와 b는 왜 같다고 나오느냐, 다른거 아니냐고 혹시 생각하신다면 Java의 경우 스트링 연산으로 인한 메모리의 낭비를 줄이기 위해 내부적으로 String 이 사용하는 메모리 공간을 나름 효율적으로 관리하여 중복되는 문자열은 되도록이면 재 할당하지 않도록 작동하기 때문에 위와 같은 단순한 코드에서는 당연히 a와 b가 값이 같을 수 밖에 없기 때문에 한 번만 할당한 뒤 공유하는 방식을 선택합니다. 그래서 a와 b가 같다는 결과가 나온거구요.
설마 그럴까 싶긴 하지만 혹시 JVM이 둘을 다르게 판단했다면 false false가 나올 수도 있습니다.
위와 같은 간단한 코드에서는 그럴 일이 별로 없지만 복잡하게 돌아가는 실무 코드에서는 충분히 발생할 수 있고 그렇기 때문에 문자열 비교의 경우는 == 이 아니라 equals() 를 써야 한다고 강조하는 거구요.
이번에는 본론입니다. C# 의 경우에는 어떨까요?
최초 다른 사람이 먼저 작성한 C# 코드를 봤는데 대부분 ==으로 비교를 했더군요..
상당히 혼동이 왔습니다.
Java의 경우는 저렇게 하지 말라고 항상 가이드가 되어있고 상식인데 C#은 안그러니 이걸 짠 사람이 잘 못 짠건지, C#은 상관 없는 건지를 판단하기가 좀 애매했습니다.
결론적으로는 C#에서의 문자열 비교는 == 로도 Equals() (C#은 Pascal Case를 쓰죠..) 와 동일하게 사용 가능합니다. 즉, String class 의 값 비교는 ==로 해도 Equals() 와 동일합니다.
그 원리는 무엇일까요?
Java를 만들 때 James Gosling의 철학에 의해 불필요하다고 판단되어 누락되었던 C 계열의 기능 중 하나인 연산자 overloading 에 의한 기능입니다.
원칙적으로는(Object class) == 연산은 Java와 동일한 객체 동일성 비교이지만(System.Object.ReferenceEquals) string class에서는 연산자 overrloading의 기능을 이용하여 == 연산을 Equals() 연산을 수행하게 만들었고 즉, 이는 string class의 경우는 == 를 쓰나, Equals() 를 쓰나 모두 Equals()가 실행되도록 한 것입니다.
그러면 이제 모두 끝났나, C#은 그냥 문자열 비교시에 ==만 쓰면 끝인가?
그런데 그게 그렇게 간단하지만은 않았습니다.
위의 사실을 알고 나서는 마음 편하게 C#에서는 == 를 쓰다가 한 번 버그로 엄청 고생을 한 적이 있습니다.
LinQ를 이용한 데이터 처리중에 분명히 값이 같은데 ==를 사용시 다르다고 나온 적이 있었죠.
물론 Equals()는 되구요.. 아래와 같은 경우입니다.
static void CompareString()
{
string a = "11";
string b = "1111".Substring(0, 2);
object c = b;
Console.WriteLine(a.Equals(b) + "\t" + a.Equals(c));
Console.WriteLine((a == b) + "\t" + (a == c));
}
결과가 어떻게 될까요?
위와 같은 결과가 나옵니다.
마침, Visual Studio 2013에서는 마지막에 값이 다른 (a == c)의 경우에는 "의도하지 않은 참조 비교가 있을 수 있으니 값 비교를 하려면 오른쪽을 "string" 형식으로 캐스팅하라" 라는 경고를 보여주긴 하네요..
차이는 c의 경우는 실제로는 string 값이 들어가지만 객체가 object이기 때문에 == 비교시 string의 Equals() 비교가 아닌 System.Object.ReferenceEquals() 이 이루어지게 됩니다. 따라서 다르다고 판단이 되죠..
위의 코드는 일부러 오류 케이스를 보이기 위해 명확하게 적어서 눈에 띄지만 Generics를 쓰지 않은 리스트나 dictionary 등에 넣거나 DataTable 에서 들어간 데이터를 접근시 indexer로 값을 가져올 때 등은 실제로는 string 값이지만 객체 형식이 string이 아닌 경우가 있기 때문에 조심해야 합니다.
결론은 다음과 같습니다.
string class간 값 비교시에는 C#은 == 를 써도 됩니다. 다만 object 등의 다른 클래스로 형식이 지정되었을 경우는 string class의 Equals() 가 overriding이 되지 않으므로 주의해야 합니다.