virtual과 관련된 몇 가지 현상에 대해 좀 더 살펴보자.

자바의 경우
자바의 경우 인터페이스를 구현하거나 다른 클래스를 상속할 경우 항상 virtual로 간주되기 때문에 virtual이라는 키워드가 존재않는다. 따라서 원하지 않는 virtual 적인 행위를 방지하기 위해 final이라는 키워드를 사용할 수 있다. 

클래스의 상속을 막기 위해 final class로 선언할 수 있고
public final class Babo { ... }

상속은 허용하되 일부 메소드의 override를 막기 위해 final 메소드로 선언할 수 있다.
pubilc class Babo
{
    public final void tellMe() { ... }
  }

참고로 final은 멤버 변수를 상수로 선언하기 위해 사용되기도 하고
public class Babo
{
    private final String name = "바보";
}

메소드의 파라메터에 로컬 변수로 선언되기도 한다.
public void tellMe(final String name) { ... }


닷넷의 경우 (C#의 경우)
C#의 경우에는 지난 번에 포스팅했듯이 인터페이스의 구현과 클래스간의 상속을 구분한다. 인터페이스의 구현은 polymorphism 성격을 띄지 않기 때문에 자식이 부모의 메소드를 override 할 수 없다. 또한 상속의 경우에도 자식이 override 할 수 있는 것과 하지 못하게 할 것을 구분하기 위해 virtual 키워드를 쓴다. virtual 이 아닌 부모의 메소드는 override 할 수 없다.
자바의 경우 처럼, C#에도 override를 방지하는 키워드 sealed가 있다. 즉, sealed 메소드가 있다. 하지만 자바와 조금 다른데, sealed 메소드는 반드시 상속을 받는 자식 클래스가 부모의 virtual 메소드를 override하면서 자신의 자식 클래스, 즉, 손자 클래스가 더 이상 override하지 못하도록 막는 역할을 한다. 반드시 override 하는 상황에서만 사용된다는 특징이 있다. 즉, 항상 sealed는 override와 함께 다닌다.

조금 다른 얘기인데 인터페이스 구현과 상속시에 IL 코드의 변화를 살펴보면 재밌는 현상을 발견할 수 있다.
  interface IMsg
  {
    void Message();
  }
  class Parent : IMsg
  {
    public virtual void Message()
    {
      Console.WriteLine("부모가 불렸네.");
    }
  }
  class Child : Parent
  {
    public sealed override void Message()
    {
      Console.WriteLine("자식이 불렸네.");
    }
  }

우선, C#에서 인터페이스 IMsg에 선언되어 있는 메소드 Message()의 IL 코드를 보면 abstract virtual로 자동 선언된다.
  .method public hidebysig newslot abstract virtual instance void  Message() cil managed

그리고, 이 인터페이스의 메소드를 (virtual 없이) 구현하고 있는 Parent 클래스의 Message() 메소드는 IL에서 virtual final로 선언된다.
  .method public hidebysig newslot virtual final instance void  Message() cil managed

하지만, 이 인터페이스를 구현하면서 위 소스코드처럼 해당 메소드에 virtual을 붙이면 Parent 클래스의 Message() 메소드는 IL에서 그냥 virtual로 선언된다.
  .method public hidebysig newslot virtual instance void  Message() cil managed

마지막으로, 인터페이스를 구현한 Parent를 상속받은 Child 클래스의 sealed override Message() 메소드는 IL에서 virtual final로 선언된다. 즉, 더이상의 override는 불가하다.
  .method public hidebysig virtual final instance void Message() cil managed

정리하면, C#에서도(??) 인터페이스의 구현시에 자동으로 virtual로 선언되나 final이 있기 때문에 virtual적인 형태를 띄지 않아 override가 되지 않는 것으로 추정되며, 구현 클래스에서 명시적으로 virtual을 붙이게 되면 final이 아닌 virtual로 선언되기 때문에 override가 가능하게 되는 것 같다. 또한 위에서 보듯이 이를 상속받은 자식 클래스에서 sealed 메소드로 선언해버리면 다시 vritual fianl이 되어 더 이상의 override는 불가능하다.

자바는 모든 것을 virtual적인 것으로 간주하여 풀고 final을 써서 필요한 부분을 막고, C#의 경우 모든 것을 일단 막고 virtual 키워드를 써서 필요한 부분을 풀고 ... 이런 것인가 ?

Posted by 장현춘

C#은 자바의 장점을 수용하고,  자바가 모델로 삼은 C++의 장점도 일부 수용하였기에 C#은 자바보다 표현 능력은 약간 낫다고 할 수 있다. C#에는 있지만 자바에는 없는 기능으로는 C++의 function pointer의 객체 버전이라 할 수 있는 delegate, 멤버 변수이외에 별도의 Property 둔 것, delegate 기반의 Event, indexer 등을 들 수 있다. C#의 Interface는 자바의 Interface와 달리 위에서 언급한 property, event, indexer 등을 선언할 수 있다. 또한 이글에서 말하고자하는 아주 작지만 재밌는 차이도 있다. 예전에 유익하게 읽었던 Effective Java 기억에 Effective C#을 읽다가 의심이 나서 테스트를 하게 되었다.

// C#
  interface IMsg
  {
    void Message();
  }
  class Parent : IMsg
  {
    public void Message()
    {
      Console.WriteLine("부모가 불렸네.");
    }
  }
  class Child : Parent
  {
    public new void Message()
    {
      Console.WriteLine("자식이 불렸네.");
    }
  }

// Java
public interface IMsg
{
  void Message();
}
public class Parent implements IMsg
{
  public void Message()
  {
    System.out.println("부모가 불렸네.");
  }
}
public class Child extends Parent
{
  public void Message()
  {
    System.out.println("자식이 불렸네.");
  }
}

두 언어 중 하나만 아는 사람이 봐도 위 코드는 같게 구현되어 있음을 알 수 있다. 이 경우 아래와 같은 코드에 의해 찍히는 값은 어떻게 될까 ?
  IMsg message = new Child();
  message.Message();

C#의 경우는 "부모가 불렸네", Java의 경우 "자식이 불렸네." 이다.
Effective C# (빌와그너 저, 김명신 역)는 이렇게 적고 있다. C#에서 Inferface의 구현은 virtual 메소드의 overriding과는 전혀 다른 것이며, Interface는 그것을 구현한 클래스를 바라보는 제한된 창이며 계약이다.
Java와 같은 결과를 얻으려면 어떻게 할까 ?
우선, Child 클래스의 선언시 Interface를 구현하는 것으로 지정한다. 즉,
class Child : Parent, IMsg
자바와 마찬가지로 C#도 클래스의 단일 상속, 인터페이스의 복수 상속을 규정하고 있다.

혹은 다음과 같은 Parent 클래스의 IMsg 구현시 메소드를 virtual로 선언하고 Child에서 override하면 된다.
class Parent : IMsg
{
  public virtual void Message()
  {
class Child : Parent
{
  public override void Message()
  {

이 테스트에서 추론할 수 있는 것은 자바의 경우 Interface의 구현과, 상속을 통한 polymorphism이 동일하게 동작하는 듯 하며, C#은 이를 분명히 구분하고 있다.

Posted by 장현춘