닷넷프로그래밍정복 김상형저 가메출판사

인터 페이스는 본체가 정의되지 않은 추상메소드를 가진 다는 것과 객체를 생성할수 없다는 점에서 추상클래스와 유사하다.

그러나 추상클래스는 추상메소드를 선택적으로 가지는데 비해 인터체이스의 메소드는 전부 추상이라는 점이 다르다.

 

연산자는 함수형 멤버이므로 이론적으로 인터페이스에 포함될수 있어야 한다. 하지만 C#은 비주얼 베이직과 호환성 문제로 인해

연산자를 인터페이스의 멤버에서 제외시켯다.

 

인터페이스의 멤버는 단순한 목록일뿐이므로 어떠한 지정자도 붙이지 않는다. 리턴타입과 목록같은 시그니쳐만 밝히면 된다.

 

상속에는 구현상속과 인터페이스 상속이 있다.

구현상속은 부모의 코드의 종속되어 버그까지 물여받을수 있는 단점이 있다.

인터페이스 상속은 구현코드는 물려받지 않고 구현해야 할 메소도의 목록만 상속받는 것인데, 종속성이 없고 내부구현에 상관없이 자신의 서비스를 외부에 노출시킬때 사용한다.

 

인터페이스간의 중간 상속이 가능하고 자식인터페이스는 부모의 인터페이스까지 목록에 가지고 있다.

하지만 C#은 다중상속은 지원하지 않는다. 효용보단 사용하기가 훨씬 까다로워서 삭제되었다. 굳이 원한다면 인터페이스를 다중상속한 다음에 구현해서 우회적으로 구현할수 있다.

 

다중 인터페이스 상속시 각기 다른 interface에서 같은 이름의 method가 있다면 하나만 구현하면 된다.

 

인터페이스 활용

1,IEnumerable과 EnumClass를 이용한 열거하기

2.IEnumerator의ㅏ GetEnumerator 메소드를 이용한 반복기.

3.ICloneable을 이용한 깊은 복사 - 참조만 복사하는 것을 얕은 복사, 대입에 의하 완전히 독립적인 생성하는 것을 깊은 복사라 한다.

 

메모리 관리.

1.가비지 콜렉터. - 백그라운드에서 항상 실행 중이며 더 이상 사용되지 않는 메모리, 즉 쓰레기를 찾아 회수한다. 그래서 개발자는 new 연산자로 필요한 만큼 객체나 메모리를 할당해서 쓰다가 그냥 나가 버리면 된다.

C#에는 delete라는 키워드가 없고 모두 GC에서 관리한다. 이런 특징은 일일이 할당한 메모리를 해제해야 하는 C++에 비해서 굉장히 편리할뿐 아니라 안전성도 높다. 자원해제를 위한 복잡한 제어구조를 만들지 않아도 되며 예외나 실수에 의해 메모리가 누출될 걱정따윈 필요없다. GC는 쓰레기 수집뿐 아니라 힙을 관리한다. 할당 요청이 있을때 힙의 낮은 번지부터 순서대로 할당하여 메모리를 깔끔하게 관리하는데 객체가 만들어지고 회수되기를 몇번 반복하면 힙의 중간중간이 비게 된다. 이런 현상을 단편화라고 한다. 이럴때 GC는 남아있는 메모리를 이동시켜 큰 덩어리를 만드는 Compaction을 수행한다. 큰 덩어리를 만들때 쓰레기를 수집하고 해제한 다음에 남은 객체들을 앞쪽으로 이동시켜 구획정리한다.

GC는 객체가 더 이상 필요하지 않을때 해제한다. 그렇다면 언제 객체가 필요하지 않다고 판단할까. 객체들은 보통 루트 객체 아래에 자식들이 계층적으로 존재하는 트리구조를 이룬다. 객체끼리 계층구조를 이룬다고 이해하면 되는데 더 정확하게는 상호참조가 가능하기때문에 그래프 구조라고 한다. GC는 루트객체로부터 순회를 시작하여 도달하지 않는 객체를 찾아 해제한다. GC는 세대를 관리해서 프로그램규모가 커져서 객체수가 늘었을때 검색시간이 늘었을때를 위해서 검색시간을 단축한다. 세대를 관리하는 이유는 최근에 만든 것일수록 쓰레기일 확률이 높고 오랫동안 살아남아 있다면 장시간에 걸쳐 사용되는 전역적인 객체일 확률이 높기때문이다. 높은 세대는 그만큼 점검을 자주할 필요가 없고 낮은 세대들만 가끔 점검해도 충분한 메모리를 손쉽게 확보할수 있다.

 

2.IDisponsable - GC는 충분히 정교하지만 관리자원만 관리한다. 메모리와 무관한 파일, 하드웨어 환경변화, 네트워크 접속 등은 GC가 관리하지 않기때문에 직접해줘야 한다. 이럴때 파괴자를 사용하여 객체가 사라질때 비관리 자원을 직접해제해야 한다. 일반적인 소멸자는 직접호출하는 것이 아니기때문에 자원해제 시점을 예측할수 없다. 그래서 IDisponsable interface가 필요하다.이 interface의 Dispose는 명시적으로 자원을 해제한다. GC를 기다리는 것이 아니라 수동으로 직접 해제함으로 불확실성이 사라진다.

ex. Socket Class example

class Socket : IDisposable
    {
        private int SocketPort;
        public Socket(int port)
        {
            SocketPort = port;
            Console.WriteLine("{0} 포트로 소켓을 연결한다", port);

        }
        public void Dispose()
        {
            SocketPort = 0;
            Console.WriteLine("소켓연결을 해제한다");
        }
    }

static void Main(string[] args)
        {
            Socket s = new Socket(1234);
            Console.WriteLine("주거나 받거나 통신했다 치고");

            s.Dispose();
        }

 

하지만 해당 클래스 사용자가 Dispose 호출을 잊을수 있다. 그렇다면 최소한의 방어로 소멸자 역시 구현해야 한다. 단순히 두 메소르를 그냥 만들어 놓는 것이 아니라 관리 자원과 비관리 자원을 적당히 나누어서 해제해야 하며 또한 이중 해제 하지 않도록 유의해야 한다.

ex) Socket class Example Extesion


 class Socket : IDisposable
    {
        private int SocketPort;
        private bool Disposed = false;
        public Socket(int port)
        {
            SocketPort = port;
            Console.WriteLine("{0} 포트로 소켓을 연결한다", port);

        }

        public void Dispose()
        {
            //SocketPort = 0;
            //Console.WriteLine("소켓연결을 해제한다");
            Dispose(true);
            GC.SuppressFinalize(this);
        }

        public virtual void Dispose(bool bManage)
        {
            if (Disposed) return;

            Disposed = true;

            if (bManage)
            {
                //여기서 관리 자원을 해제한다.
            }
            SocketPort = 0;
            Console.WriteLine("소켓연결을 해제한다");
        }

        ~Socket()
        {
            Dispose(false);
        }
    }

Dispose(bool)메소드가 하나 더 오버로드 되어 있는데 이 메소드는 bManage인수에 따라 모든 자원을 해제하거나 또는 비관리 자원만 해제한다. 코드의 중복을 방지하기 위해 실제 자원해제코드는 이 메소드에 작성되어 있고 소멸자와 Dispose()에서 이 메소드를 호출한다. Dispose(bool) 메소드는 고의 실수든 두번 이상 호출되더라도 이중 해제를 하지 않도록 한잔한 해제를 해야 해서 해제여부를 스스로 private field에 저장한다.

두개의 Dispose 메소드와 소멸자는 자원해제를 분담하는데 각 메소드는 호출되는 경로와 하는 역할이 다르다.

소멸자는 자신이 직접 할당한 비 관 리 자원만 해제해야 하며 관리자원을 해제해서는 안된다. 왜냐하면 소멸자가 GC에 의해 호출되는 시점이 예측할수 없고 아직 자원해제 되지 않는 객체가 이 객체를 참조하고 잇는 상황이 있을수 있기때문에 GC에 맡겨야 한다. 상속받은 Dispose()메소드는 Dispose(true)를 호출하여 관리 자원과 비 관리 자원을 모두 해제하야 객체가 생성되기 전의 상태로 완벽하게 복구하는 역할을 담당한다. GC.SuppressFinailize메소드는 GC가 이 객체의 소멸자를 부르지 않게 지시한다. Dispose에서 관리,비관리 자원을 모두 해제했으므로 소멸자를 호출할 필요가 없다. 만약 이 처리가 없다면 해제한 자원을 다시 해제하려고 할 것이다.

 

이 글은 스프링노트에서 작성되었습니다.

by 무위자연 2008. 8. 26. 09:20