달력

12

« 2024/12 »

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31

..  이번에는 Constructor Chaining(또는 Cascading)이라는 개념입니다.

대단한건 아니고 생성자를 하나가 아니라 여러 파라미터들을 옵션으로 선택할 경우 코드를 줄이기 위한 방법 중 하나입니다. 


물론 C# 뿐만이 아니라 Java에서도 가능하지만 Java에서는 딱히 이런 명칭을 들어본 적은 없는것 같네요..  C#은 약간 별도의 문법이 있어서 이런 명칭이 붙은 것 같습니다.  당연히 될 거라 생각하고 Java의 문법을 적용했는데 자꾸 Syntax Error가 발생하여 찾아보다가 발견한 내용입니다.



  예를 하나 들겠습니다.

UserException 이라는 사용자 정의 Exception을 하나 생성하려고 하는데 생성자에 파라미터로 에러메시지(message), 에러코드(code)라는 두 개의 파라미터를 받으려고 합니다.

그런데 항상 두 개의 파라미터를 꼭 받아야 하는 건 아니고 디폴트 코드가 있으면 메시지만, 거꾸로 디폴트 메시지가 있다면 코드만 받아서 나머지 하나는 디폴트 값을 넣으려고 합니다.  물론 둘 다 받을 경우도 있으니 총 3개의 경우의 수가 생기겠네요.


단순하게 생각해서 생성자 3개 만들면 끝납니다.  그런데 만일 단순히 이 값을 받아 저장하는 것이 아니라 값들에 따른 로직이 좀 더 있다라면 해당 로직은 세 개의 생성자에 동일하게 복사해서 넣어야 합니다.  코딩에서 주의해야 할 코드 중복이 발생하겠죠..

그렇다면 어떻게 하는 것이 좋을까요?  가장 다양한 케이스인 두 개의 파라미터를 모두 받는 경우에만 해당 로직을 구현해 놓고 나머지 두 개의 생성자에는 디폴트 값과 입력받은 값을 가지고 두 개의 파라미터를 가지는 생성자를 호출하면 깔끔하게 끝납니다.

백문이 불여일타라, Java의 경우는 대략 다음과 같은 코드로 만들 수 있습니다.


	public UserException (int code) {
        this(code, "Default Message");
    }

    public UserException (String message) {
        this(0, message);
    }

    public UserException (int code, String message) {
        // 값을 할당하고 처리하는 로직.....
    }



this() 를 이용하여 생성자 내부에서 생성자를 호출할 수 있죠.

물론 this()  호출은 해당 생성자의 첫번째에 위치해야 합니다.



  그런데 문제는 C#에서 동일한 코드를 사용했더니 자꾸 오류가 발생하는 겁니다.

검색을 해 보니 C#에서는 이런걸 Constructor Chaining(Cascading)이라 부르고(Java에서도 이 기법 자체를 그렇게 부르는지는 모르겠습니다.  지금까지는 들어본 적이 없어서..) 약간 문법이 다릅니다.  해당 내용을 C# 버전으로 구현하면 다음과 같습니다.


    public UserException (int code) : this(code, "Default Message")
    {

    }

    public UserException (String message) : this(0, message) {

    }

    public UserException (int code, String message) {
        // 값을 할당하고 처리하는 로직.....
    }


위 코드와 같이 함수명 뒤에 : this() 형태로 추가를 해야 하지 Java 처럼 내부에서 this를 호출하면 Syntax Error가 발생합니다.

물론 생성자 호출 후 자체적인 초기화 코드가 있으면 Java에서는 this 밑에, C#에서는 코드 본문 영역에 넣으면 됩니다.


이렇게 별 것도 아닌데 사소한 문법의 차이로 시간 낭비할 때가 해결된 후에 제일 허탈하더군요..

그게 어찌 보면 제가 이 꼭지를 만든 이유이기도 합니다.


다른 분들은 삽질을 덜 하길 바라면서...

'C# > Java 개발자를 위한 C#' 카테고리의 다른 글

C# 에서의 문자열 비교, == or Equals?  (4) 2017.02.28
이곳의 용도는...  (0) 2017.02.27
:
Posted by hanavy

  첫번째 주제는 문자열 비교에 대한 부분입니다.

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 별로 결과가 다를 가능성이 없다고는 못하겠는데 아마도 대부분의 경우 다음과 같이 나올 겁니다.


true    true
true    false


위에 적은 대로 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));
        }


결과가 어떻게 될까요?


True    True
True    False


위와 같은 결과가 나옵니다.

마침, 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이 되지 않으므로 주의해야 합니다.

'C# > Java 개발자를 위한 C#' 카테고리의 다른 글

생성자(Constructior) Chaining(Cascading)  (0) 2017.03.09
이곳의 용도는...  (0) 2017.02.27
:
Posted by hanavy