Media Log


1. 확장 메소드(Extension Method)


확장 메소드는 지금까지 우리가 보아왔던 메소드와는 다르게 조금 독특한 메소드라고 할 수 있습니다. 이 확장 메소드는, 기존 클래스의 기능을 확장시켜주는 메소드라고 볼 수 있습니다. 아래는 확장 메소드의 선언 형식입니다.

namespace 네임스페이스명
{
    public static class 클래스명
    {
        public static 반환형식 메소드명(this 확장대상형식 식별자, 매개변수..)
        {
            ..
        }
        ..
    }
}

선언 형식을 보시면, 정적(static) 클래스를 먼저 정의하고 그 안에 확장 메소드가 정의되었습니다. 확장 메소드 역시 정적(static) 메소드여야 합니다(정적 메소드는 객체를 만들지 않고도 바로 호출이 가능). 그리고 메소드의 첫번째 매개변수에서 this 한정자가 존재해야 합니다. 바로 확장 메소드와 관련된 예제를 살펴보도록 합시다. 

using System;
using Extension;

namespace Extension
{
    public static class ExtensionMethod
    {
        public static int Multiplication(this int var, int a, int b)
        {
            int result = var;

            for (int i = 0; i < b; i++)
                result *= a;

            return result;
        }
    }
}

namespace Example
{
    class Program
    {
        static void Main(string[] args)
        {
            Console.WriteLine("{0}", 5.Multiplication(2, 3));
        }
    }
}

결과:

40

계속하려면 아무 키나 누르십시오 . . .


코드를 보시면, 6행에서 확장 메소드인 Multiplication()을 포함하는 정적 클래스인 ExtensionMethod가 보입니다. 이 메소드가 어떻게 사용되는지는 22행을 보면 알 수 있습니다. 마치 인스턴스 메소드처럼 5의 뒤에 .을 붙여 메소드를 호출하고 있습니다. 여기서 5는 Multiplication()의 매개변수 var에 들어가며, 2는 a에, 3은 b에 들어가게 됩니다. 10~15행을 살펴보면 result란 변수에 var의 값을 담아, result에 a를 b번 곱하고 이 결과값을 호출부로 반환하여, 반환된 값을 출력시킵니다.


이러한 확장 메소드는 아래와 같은 상황에서 유용하게 쓰일 수 있습니다.

이미 빌드된 라이브러리를 참조해서 사용하고 있는데, 이 라이브러리 내부에 있는 기존의 클래스를 좀 더 확장시키고 싶어. 그런데 나에게는 이 라이브러리의 소스 코드가 없기 때문에 클래스를 직접 수정할 수는 없을 것 같아.

이 경우에는 확장 메소드를 통해 기존 클래스의 소스 코드를 변경하지 않고 기능을 확장할 수 있습니다. 물론, 이와 같은 경우에도 우리가 앞에서 배운 상속을 통해서도 기능을 확장할 수 있습니다. 상속도 좋은 방법이지만, 클래스가 sealed로 한정되어 있는 경우에는 확장 메소드의 사용을 고려해 볼 수 있습니다.


2. 분할 클래스(Partial Class)


클래스의 구현이 길어질 경우 두 개 이상의 소스 파일로 분할하여 동시에 작업을 수행하거나, 관리의 편의를 위해 클래스를 분할하기도 합니다. 클래스를 분할하려면 partial 키워드를 사용하면 됩니다. 클래스 말고도 앞으로 배울 인터페이스, 구조체에도 partial 키워드를 사용할 수 있습니다. 아래는 분할 클래스의 예제입니다.

using System;

namespace Example
{
    partial class Nested
    {
        public void Test() { Console.WriteLine("Test()"); }
    }
    partial class Nested
    {
        public void Test2() { Console.WriteLine("Test2()"); }
    }
    partial class Nested
    {
        public void Test3() { Console.WriteLine("Test3()"); }
    }
    class Program
    {
        static void Main(string[] args)
        {
            Nested nested = new Nested();
            nested.Test();
            nested.Test2();
            nested.Test3();
        }
    }
}

결과:

Test()

Test2()

Test3()

계속하려면 아무 키나 누르십시오 . . .


partial 키워드가 붙은 클래스는 컴파일 시 컴파일러에 의해 하나로 합쳐집니다. 분할에는 제한이 없으며, 여러 번 분할해도 상관이 없습니다.


3. 중첩 클래스(Nested Class)


중첩 클래스는 클래스 내에 또 클래스가 정의된 클래스를 말합니다. 전에 배운 중첩 for, while, if문 등과 같이 클래스도 중첩이 가능합니다. 이 중첩 클래스는 주로 외곽 클래스에서만 사용하고자 할때, 외부에 정의하는 것보다 관련있는 클래스를 내부 클래스로 두어 코드를 쉽게 이해하기 위해 사용됩니다. 참고로, 내부에 쓰인 클래스는 제한자가 명시되어 있지 않으면 private로 보호 수준이 지정됩니다. 즉, A라는 클래스 안에 private로 지정된 B라는 클래스가 존재하면, B 클래스는 A 클래스 밖에서 보이지 않습니다. A 클래스 내에서만 사용이 가능합니다.

class 클래스명
{
    class 클래스명
    {
        ..
    }
}

아래는 중첩 클래스가 쓰인 예제입니다.

using System;

namespace ConsoleApplication21
{
    public class OuterClass
    {
        private int a = 70;

        public class InnerClass
        {
            OuterClass instance;

            public InnerClass(OuterClass a_instance)
            {
                instance = a_instance;
            }

            public void AccessVariable(int num)
            {
                this.instance.a = num;
            }

            public void ShowVariable()
            {
                Console.WriteLine("a : {0}", this.instance.a);
            }
        }
    }

    class Program
    {
        static void Main(string[] args)
        {
            OuterClass outer = new OuterClass();
            OuterClass.InnerClass inner = new OuterClass.InnerClass(outer);

            inner.ShowVariable();
            inner.AccessVariable(60);
            inner.ShowVariable();
        }
    }
}

결과:

a : 70

a : 60

계속하려면 아무 키나 누르십시오 . . .


코드를 보시면, 13~16행에서 InnerClass의 생성자에 OuterClass의 객체를 넘겨주어 instance 객체에 OuterClass의 객체인 a_instance를 가져옵니다. 그리고 18~21행에서는 매개변수 num을 받아 num 값을 가지고 instance 객체의 멤버 변수 a의 값을 수정합니다. 여길 보아, 안쪽에 정의된 클래스에서 바깥쪽에 정의된 클래스의 private 멤버에 접근할 수 있음을 의미합니다. 그리고 23~26행은 그 instance 객체의 멤버 변수 a의 값을 출력시킵니다.

  1. dreamer at 2012.10.11 09:57 신고 [edit/del]

    잘봤습니다. 날마다 새로운 내용알아가서 좋네요.^^ 설명도 쉽게해주셔서 이해도 빠르고. 좋은강좌 올려주셔서 감사합니다.ㅎ

    Reply
  2. 김승현 at 2012.10.20 20:23 신고 [edit/del]

    감사합니다~

    Reply
  3. 질문 at 2012.11.05 17:48 신고 [edit/del]

    InnerClass에서 OuterClass를 선언할 수 있는게 이해가 안됩니다.
    OuterClass를 정의하고 있고 그 안에서 InnerClass를 같이 정의 중인데 자기보다 상위 클래스를 선언한다는게 이해가 안되네요.

    Reply
    • BlogIcon EXYNOA at 2012.11.05 22:07 신고 [edit/del]

      이는 내부 클래스에서 내부 클래스를 감싸주고 있는 외부 클래스에 액세스 하기 위해 작성한 부분입니다. 외부 클래스에 액세스 하기 위해 외부 클래스를 생성자로 중첩 클래스에 전달했습니다.

      저 내부 클래스도 하나의 멤버로 보셔야 합니다. 저런 틀(class)만 존재할 뿐이지, 실제로는 39행과 같이 외부 클래스를 생성자로 넘겨주어야 제기능을 합니다. 15행의 저 코드는 아무런 문제가 없게 되는거죠.

  4. 박무진 at 2012.12.05 12:08 신고 [edit/del]

    확장메소드에서 5.Multiplication(2, 3) 이렇게 호출하는 것이 맞는지요?
    Multiplication(5, 2, 3) 이렇게 호출하는것이 아니라...

    Reply
  5. 키리형 at 2013.02.25 17:00 신고 [edit/del]

    감사합니다 ~

    Reply
  6. C#입문자 at 2013.04.05 16:33 신고 [edit/del]

    확장메소드 이해가 안 갔었는데, using 으로 선언하는 부분을 이해하고 나니 확 와닿았네요..^^ 감사합니다!!
    매번 소스 작성해서 컴파일할때마다 개념 이해하는데 도움이 많이 되고 있습니다^^

    Reply
  7. 똘스터 at 2013.04.10 14:29 신고 [edit/del]

    C# 독학하고있는데 책으로도 이해가 안됐던 부분들을 잘 설명해주셨네요!!

    너무감사합니다!!

    여기있는 포스팅들 다 읽어보려고요!

    잘보겠습니다!

    Reply
  8. Nate Moon at 2013.06.04 17:41 신고 [edit/del]

    잘 보고 갑니다 ^-^!

    Reply
  9. 용쿤 at 2013.10.14 17:53 신고 [edit/del]

    강의 감사드립니다. 저 질문이 있는데요.

    중첩class 내용 중 15번행에서 "OuterClass instance;" 라고 선언된 부분이 있는데요

    여기서 instance는 OuterClass의 객체이고 a = 70이란 정보가 자동으로 들어있는 건가요?

    아니면 OuterClass instance = new OuterClass(); 와 같은거라고 봐야되는지요

    클래스 이름 뒤에 바로 객체를 적는게 처음 접하다보니 이해가 가질 않습니다

    Reply
    • BlogIcon EXYNOA at 2013.10.15 20:37 신고 [edit/del]

      네 그렇습니다. 클래스 OuterClass의 멤버 변수 a는 70으로 초기화 되어있고, 38행에서 생성한 OuteClass 객체 outer를 InnerClass의 생성자로 넘겨주어 InnerClass 내의 instance를 초기화하는 것입니다.

  10. ZiNee at 2013.10.21 14:56 신고 [edit/del]

    강좌 감사합니다.

    Reply
  11. iamksu at 2014.09.25 16:54 신고 [edit/del]

    이번 강좌도 잘 봤습니다.

    Reply
  12. ehrbs at 2014.11.28 14:18 신고 [edit/del]

    강좌 감사합니다.
    제가 어떠한 책을 보면서 공부하고 있는데 이해가 안가서 질문 드립니다. ㅠ
    코드가
    public class HardDisk
    {
    class Platter{}
    Platter [] platter;
    }
    이렇습니다. 여기서 Platter[] platter의 의미가 대체 무엇인가요 ㅠ.
    왜 이렇게 적었는지 이유를 몰라서 질문드립니다.

    Reply
  13. C#을 배우고 싶어요 at 2014.12.23 17:58 신고 [edit/del]

    엑시노아님 이번 회차 강좌에 궁금한 점이 있습니다.
    바로 39행의 innerClass의 객체를 생성하는 부분인데요.
    innerClass가 생성자이기 때문에 객체를 생성하면서 바로 호출까지 하는걸로 알고 있습니다.
    그렇기 때문에 innerClass를 호출하며 매개변수로 outerClass의 객체인 outer를 넘겨주잖아요??

    그 부분이...이해가 어렵습니다..
    매개변수를 받는 쪽은 (변수타입 변수명)의 형태를 띄도록 되어 있는것 아닙니까??
    그렇다면 innerClass 생성자 메소드는 outerClass라는 타입을 a_instence 라는 이름으로 받는겁니까??

    Reply
    • BlogIcon EXYNOA at 2014.12.25 13:16 신고 [edit/del]

      네, 말씀하신대로 객체가 생성되면서 생성자가 호출되어 내부 클래스를 외부 클래스에 넘겨주는게 맞습니다. 내부 클래스 생성자가 a_instence라는 이름으로 넘겨받아 사용하는 것도 맞습니다.

  14. 오곡 at 2016.09.12 15:15 신고 [edit/del]

    좋은 공부하고 갑니다~!

    Reply
  15. 초보자 at 2016.10.20 08:59 신고 [edit/del]

    감사합니다!!

    Reply
  16. 쟝미 at 2017.04.06 16:23 신고 [edit/del]

    확장메서드를 사용하는 궁극적인 이유는 무엇인가요?? 클래스를 확장시킨다는게 어떠한 경우를 의미하는지 잘 모르겠습니다. ㅠㅠ 왜 쓰는지, 왜 생긴 문법인지가 궁금합니다.
    설명이 항상 깔끔하셔서 물어봅니다!!

    Reply
  17. ldi3492 at 2018.01.23 15:36 신고 [edit/del]

    앞선 14년 자료에 택배조회 프로그램 만드셨던데 제가 임의로 배송조회 시스템 비슷한걸 만들어야 하는데 좀 질문좀 드려도 될까요??
    메일로 답장좀 부탁드립니다. ldi3492@naver.com 입니다.

    Reply
    • BlogIcon EXYNOA at 2018.01.23 16:02 신고 [edit/del]

      질문이 여러 개가 아니라면 방명록에 남겨주시면 감사하겠습니다. 아니면 카카오톡으로 대화를 주셔도 괜찮습니다.

  18. sujub at 2018.09.14 22:48 신고 [edit/del]

    감사합니다~~ 설명을 축약해서 이리 잘올려주신자료 처음이네요 ㅠㅠ 최고세용~~

    Reply

submit


1. 클래스의 상속(Class inheritance)


이번에 배울 건 '클래스의 상속(Class inheritance)'입니다. 어? 상속이란 말을 어디선가 들어본 적이 있는 것 같지 않나요? 짐작하는 그 상속이 맞냐구요? 네 맞습니다. 혹시나 상속이 뭔지 들어보 적 없는 분들을 위해 무엇인지 알려드리려고 합니다. 상속이란 네이버 지식백과를 빌어 다음과 같이 정의되어 있습니다. '일정한 친족적 관계가 있는 사람 사이에 한 쪽이 사망하거나 법률상의 원인이 발생하였을 때 재산적 또는 친족적 권리와 의무를 계승하는 제도'. 즉, 부모님이 돌아가셨다고 할 때 그 유산을 자식이 물려받는 것이라고 할 수 있습니다. 클래스의 상속도 이와 똑같습니다. 객체 지향 프로그래밍에선 부모 클래스와 자식 클래스가 있는데, 부모 클래스는 자식 클래스의 기반이 된다 하여 기반 클래스라고 부르기도 하고, 자식 클래스는 부모 클래스로부터 파생되었다고 해서 파생 클래스라고도 부르기도 합니다.


C#에서, 클래스를 다른 클래스로 상속하려면 다음과 같이 클래스 이름 뒤에 콜론(:)을 추가하고 상속하려는 클래스의 이름을 덧붙이시면 됩니다.

class 부모 클래스
{
    // ...
}

class 자식 클래스 : 부모 클래스
{
    // 부모 클래스의 모든 상태와 행동이 전달 됨.
}

위 예제를 보시면 자식 클래스에 부모 클래스를 상속시킵니다. 부모 클래스를 상속받은 자식 클래스는 부모 클래스의 모든 멤버를 물려받게 됩니다. (다만, 생성자는 상속이 되지 않으며 객체 생성 시 부모 클래스의 생성자가 자동으로 호출됨) 


[그림 1-1. 상속 관계]


여기서 한가지 알아두셔야 할 점은 private로 선언된 멤버는 상속할 수 없습니다. 아래는 상속의 예제입니다.

using System;

namespace ConsoleApplication8
{
    class Parent
    {
        public int num;

        public Parent()
        {
            Console.WriteLine("부모 클래스의 생성자가 호출되었습니다.");
        }
    }

    class Child : Parent
    {
        public Child(int num)
        {
            this.num = num;
            Console.WriteLine("자식 클래스의 생성자가 호출되었습니다.");
        }

        public void DisplayValue()
        {
            Console.WriteLine("num의 값은 {0} 입니다.", num);
        }
    }

    class Program
    {
        static void Main(string[] args)
        {
            Child cd = new Child(20);

            cd.DisplayValue();
        }
    }
}

결과:

부모 클래스의 생성자가 호출되었습니다.

자식 클래스의 생성자가 호출되었습니다.

num의 값은 20 입니다.

계속하려면 아무 키나 누르십시오 . . .


코드를 보시면 5행에서 Parent라는 클래스가 등장합니다. Parent 클래스 내에는 num이라는 멤버 변수와 생성자가 있습니다. 그리고 15행을 보시면 Parent 클래스를 Child 클래스에 상속시키고 있는 것을 볼 수 있습니다.


Child 클래스 내부를 보시면 생성자와 DisplayValue()라는 메소드가 존재합니다. 생성자를 살펴보면 매개변수 하나를 받고, 부모 클래스로부터 물려받은 멤버 변수 num을 매개변수의 값으로 초기화시킵니다. 33~35행을 보시면 객체를 생성하고 그 객체의 DisplayValue() 메소드를 호출합니다.


DisplayValue() 메소드를 살펴보면 num의 값을 출력하는 코드가 보이죠? 결과를 보니, num의 값은 20이라고 출력되었네요. 부모 클래스의 멤버 변수 num의 값을 출력시킨 것과 같습니다. 그리고 생성자의 호출 순서를 보니 부모 클래스의 생성자가 먼저 호출되고, 자식 클래스의 생성자는 그 뒤이어 호출됨을 확인할 수 있습니다. 즉, 부모 클래스의 생성자가 먼저 호출되고 자식 클래스의 생성자가 호출됨을 알 수 있습니다. 반대로 소멸할 때에는 역순으로 자식 클래스의 소멸자부터 호출되고, 이어서 부모 클래스의 소멸자가 호출됩니다.


그런데 한가지 이상한 게 보이죠? 19행을 보시면 자신을 가리키는 this 키워드가 사용되었습니다. this 키워드를 사용하여 부모 클래스의 멤버 변수에 접근은 할 수 있습니다. 그러나, 자식 클래스에도 num이라는 멤버 변수가 존재할 때는 부모 클래스의 멤버 변수인 num에 어떻게 접근하여야 할까요? 그럴 때는 this 키워드가 아닌 base 키워드를 사용하시면 됩니다. 다음과 같이 말이죠.

..
        public Child(int num)
        {
            base.num = num;
            Console.WriteLine("자식 클래스의 생성자가 호출되었습니다.");
        }
..

위와 같이 base 키워드를 사용하면 부모 클래스에 접근할 수 있게 됩니다. this 키워드와 비슷하죠? 


2. sealed


클래스명 앞에다 sealed 키워드를 사용하게 되면, 이 클래스를 상속시키는 건 더이상 할 수 없습니다. 즉, 더이상 이 클래스는 다른 클래스의 부모 클래스가 될 수 없습니다. sealed의 이해를 위해 전의 예제에서 Parent 클래스 앞에 sealed 키워드를 달아봅시다. 그리고 곧바로 결과를 확인해봅시다.

..
    sealed class Parent
    {
        public int num;

        public Parent()
        {
            Console.WriteLine("부모 클래스의 생성자가 호출되었습니다");
        }
    }

    class Child : Parent
    {
        public int num;

        public Child(int num)
        {
            this.num = num;
            Console.WriteLine("자식 클래스의 생성자가 호출되었습니다.");
        }
        public void DisplayValue()
        {
            Console.WriteLine("num의 값은 {0} 입니다.", num);
        }
    }
..

컴파일을 시도 했더니, 다음과 같은 에러가 발생했습니다.


오류 1 'ConsoleApplication8.Child': sealed 형식 'ConsoleApplication8.Parent'에서 파생될 수 없습니다. c:\users\h4ckfory0u\documents\visual studio 2012\Projects\ConsoleApplication8\ConsoleApplication8\Program.cs 19 11 ConsoleApplication8


즉, 'sealed 형식인 Parent 클래스로부터 파생될 수 없다'란 에러입니다. sealed 키워드를 사용하면, 의도하지 않은 상속을 불가능하게 만들 수 있습니다.

3. set, get

set, get 접근자는 각각 속성을 읽거나, 새 값을 할당할 때 사용됩니다. 객체 지향 프로그래밍에서 정보 은닉(information hiding)을 위해 클래스 내부에서만 사용할 수 있도록 private로 접근을 제한해 버립니다.

  정보 은닉(information hiding)은 무엇인가요?


이는 객체 지향 프로그래밍과 밀접한 관련이 있습니다. 클래스 내부의 필드나 메소드와 같이 객체가 가지고 있는 것들을 외부에서 접근하지 못하도록 숨기는 것을 말합니다. 


우리가 클래스를 설계할 때, private나 public 등과 같은 접근 제한자를 통하여 특정 멤버를 공개할 것인지 공개하지 않을 것인지 지정해 줄 수 있었습니다. 왜 이러한 작업을 하는 걸까요? 


우리가 클래스를 설계할 때, 수십에서 수백 개에 달하는 필드나 프로퍼티(property)가 존재할 수 있습니다. 그러나 이러한 정보들을 외부로 모두 노출시켜 버리면 우리가 설계한 클래스를 사용하는 사람의 입장에서는 상당히 곤혹스러울 것입니다. 이럴 때는 필요한 정보만을 외부로 노출시킬 필요가 있습니다.


또 다른 이유는 안정성을 위한 것입니다. 객체 내부에서만 사용되는 필드나 메소드는 외부로 공개하면, 외부에서 이를 수정할 수 있기 때문에 안정성이 깨질 우려가 있기 때문입니다. 

public class Person {
    public String name;
    public int age;

    public Person() {
    // ...
    }

    // ...
 }

위와 같이 Person이란 클래스가 있다면, 외부에서 name과 age를 수정할 수 있습니다. 접근 제한자가 public으로 지정되어 있기 때문이죠. 그럼 우리는 name이나 age의 값을 신뢰할 수 있을까요? 그렇기 때문에 name과 age 필드의 접근 범위를 private로 제한해줄 필요가 있습니다. 


그럼 외부에서 이 속성을 변경할 수가 없겠죠? 그런데 프로그램을 만들다 보면, 내부에 있는 변수를 수정해야 할 상황이 벌어질 수도 있습니다. 그럴 때 쓰이는 것이 set, get 접근자입니다. get 접근자만 존재한다면 읽을 수만 있으며, set 접근자만 존재하면 쓸 수만 있으며, 두 접근자가 모두 존재하면 읽을 수도 있고, 쓸 수도 있게 됩니다.
(아래에 작성된 내용은 모두 프로퍼티와 관련된 내용입니다. 이는 이곳에서 확인하실 수 있습니다.)
using System;

namespace ConsoleApplication14
{
    public class MyClass
    {
        private string name = "John";

        public string Name
        {
            get
            {
                return name;
            }
            set
            {
                name = value;
            }
        }
    }
    class Program
    {
        static void Main(string[] args)
        {
            MyClass mc = new MyClass();

            Console.WriteLine("mc.Name : {0}", mc.Name);

            mc.Name = "Bree";

            Console.WriteLine("mc.Name : {0}", mc.Name);
        }
    }
}

결과:

mc.Name : John

mc.Name : Bree

계속하려면 아무 키나 누르십시오 . . .


코드의 7줄을 보시면 name 속성이 private로 접근이 제한되어 있음을 알 수 있습니다. 그리고 9~19행에서 get, set 접근자가 등장합니다. Name이란 이름으로 get/set 접근자를 통해 name에 접근할 수 있으며, get 영역 내에서는 name의 값을 반환하고, set 영역 내에서는 name 속성에 value 값으로 초기화시킵니다. 여기서 value은 Name으로 넘어온 값이라고 생각하시면 됩니다. 27행에서는 mc.Name이 아직까지는 John이였다가, 29행에서 이름이 "Bree"로 바뀌고, 31행에서 바뀐 이름을 출력하게 됩니다. 


그리고 get/set 접근자 내에서 value에 변화를 주거나, 주지 않을 수도 있습니다. 아래는 그 예제입니다.

    public class MyClass
    {
        private string name = "John";

        public string Name
        {
            get
            {
                return name;
            }
            set
            {
                if (value.Length < 5)
                    name = value;
            }
        }
    }
    class Program
    {
        static void Main(string[] args)
        {
            MyClass mc = new MyClass();

            Console.WriteLine("mc.Name : {0}", mc.Name);

            mc.Name = "Kelley";
            
            Console.WriteLine("mc.Name : {0}", mc.Name);
        }
    }

결과:

mc.Name : John

mc.Name : John

계속하려면 아무 키나 누르십시오 . . .


요번에는 set 접근자 영역 내를 잘 보면 value의 길이가 5보다 작아야 새 값을 할당할 수 있습니다. 만약에 5보다 같거나 크면 어떻게 될까요? 이번에는 Name에 Kelley를 넣어본 뒤에, 변화가 있나 결과를 살펴보았습니다. 결과를 봤더니, 새 값이 할당되지 않고 John 그대로 값이 유지되어 있었습니다. Kelley의 길이는 6이므로 초기화 되지않고 빠져나와 버린 것이죠.


4. 메소드 재정의(virtual, override)


부모 클래스의 메소드를 자식 클래스에서 다시 정의하고 싶을때 virtual, override 키워드가 사용됩니다. 자세히 말하자면, virtual 키워드는 자식 클래스에서 메소드를 재정의 하고 싶을때 재정의 될 부모 클래스의 메소드에 사용되며, override 키워드는 부모 클래스 내에서 virtual로 선언된 메소드를 재정의 하겠다는 표시를 하는 것과 같습니다. (이 말고도 추상 구현 등에서 사용되기도 합니다.)

using System;

namespace ConsoleApplication21
{
    class Parent
    {
        public virtual void A()
        {
            Console.WriteLine("부모 클래스의 A() 메서드 호출!");
        }
    }
    class Child : Parent
    {
        public override void A()
        {
            Console.WriteLine("자식 클래스(Child)의 A() 메서드 호출!");
        }
    }
    class Daughter : Parent
    {
        public override void A()
        {
            Console.WriteLine("자식 클래스(Daughter)의 A() 메서드 호출!");
        }
    }

    class Program
    {
        static void Main(string[] args)
        {
            Parent parent = new Parent();
            parent.A();

            Child child = new Child();
            child.A();

            Daughter daughter = new Daughter();
            daughter.A();
        }
    }
}

결과:

부모 클래스의 A() 메서드 호출!

자식 클래스(Child)의 A() 메서드 호출!

자식 클래스(Daughter)의 A() 메서드 호출!

계속하려면 아무 키나 누르십시오 . . .


코드의 7행, 14행, 21행을 보시면 각각 virtual, override, override가 등장했습니다. 알아두셔야 할 점은, 메소드를 재정의 하려면 virtual 키워드가 붙어 있어야 한다는 겁니다. 만약 virtual 키워드가 붙어있지 않다면, 컴파일러는 다음과 같은 에러를 내보냅니다.


오류 1 'ConsoleApplication21.Child.A()': 상속된 'ConsoleApplication21.Parent.A()' 멤버는 virtual, abstract 또는 override로 표시되지 않았으므로 재정의할 수 없습니다. C:\Users\h4ckfory0u\documents\visual studio 2012\Projects\ConsoleApplication21\ConsoleApplication21\Program.cs 18 30 ConsoleApplication21


재정의될 메서드가 virtual로 표시되지 않으면 재정의를 할 수 없다는 에러입니다. 즉, 재정의 될 메서드에는 virtual로 한정되어 있어야 하며, 이것을 오버라이딩 하기 위해 override 키워드를 사용합니다. 한가지 더 말씀드리자면, private로 접근 범위가 제한된 메서드는 재정의 할 수 없습니다.

5. 멤버 숨기기(new)

new 지정자를 사용하면 부모 클래스의 멤버를 숨길 수 있게됩니다. 물론, 부모 클래스에서 정의된 메소드, 멤버 변수의 이름이 자식 클래스에도 같은 이름으로 존재한다면 부모 클래스의 멤버는 new 지정자를 사용하지 않아도 숨길 수 있으나, 부모 클래스의 멤버가 숨겨진다는 경고가 발생합니다. 이 경고는 new 지정자를 사용하면 사라집니다. 

using System;

namespace ConsoleApplication21
{
    class Parent
    {
        public int x = 100;
        public void A()
        {
            Console.WriteLine("부모 클래스의 A() 메서드 호출!");
        }
    }
    class Child : Parent
    {
        public new int x = 200;
        public new void A()
        {
            Console.WriteLine("자식 클래스(Child)의 A() 메서드 호출!");
        }
    }

    class Program
    {
        static void Main(string[] args)
        {
            Parent parent = new Parent();
            parent.A();
            Console.WriteLine("x : {0}", parent.x);

            Child child = new Child();
            child.A();
            Console.WriteLine("x : {0}", child.x);
        }
    }
}

결과:

부모 클래스의 A() 메서드 호출!

x : 100

자식 클래스(Child)의 A() 메서드 호출!

x : 200

계속하려면 아무 키나 누르십시오 . . .


new 키워드를 붙이지 않아도 컴파일 하는데는 지장이 없습니다. 다만 경고가 발생할 뿐입니다.


6. 업캐스팅과 다운캐스팅(Upcasting and Downcasting)


지금 소개할 개념은 객체 지향 프로그래밍의 특징 중 하나인 다형성(polymorphism)과 밀접한 관련이 있습니다. 


  다형성(polymorphism)은 무엇인가요?


polymorphism에서 poly는 여러, 다양한(many), morph는 변형(change)이나 형태(form)의 의미를 가지고 있습니다. 사전적 정의로만 살펴보면 "여러가지 형태"를 나타내는데, 이를 객체 지향 프로그래밍으로 끌고 온다면 "하나의 객체가 여러 개의 형태를 가질 수 있는 능력"이라 말할 수 있습니다. 


우리는 이미 다형성을 접해본 적이 있습니다. 다형성의 일부인 메소드의 오버로딩(이는 ad-hoc polymorphism에 해당)과 오버라이딩(이는 inclusion polymorphism에 해당)에서 말이죠! C#도 객체 지향 프로그래밍에 근간을 두고 있으므로 이 다형성이라는 녀석은 앞으로도 계속 만나볼 것입니다.


우선 업캐스팅(upcasting)부터 살펴보도록 하겠습니다. 업캐스팅이란 말 그대로 자식 클래스의 객체가 부모 클래스의 형태로 변환되는 것을 말합니다. 반대로, 다운캐스팅(downcasting)은 부모 클래스의 객체가 자식 클래스의 형태로 변환되는 것을 말합니다. 직접 예제 코드를 보도록 하겠습니다.

class Animal {  }

class Dog : Animal {  }

Dog dog = new Dog();
Animal animal = dog; // 업캐스팅
Dog sameDog = (Dog)animal; // 다운캐스팅

개는 기본적으로 동물의 상태와 행동을 모두 가지기 때문에, Dog 클래스의 객체를 Animal 클래스의 형태로 바꾸어도 문제가 없습니다. 따라서, 우리가 "어떤 형태로 변환하겠다"와 같은 별다른 구문을 쓰지 않아도 암시적으로 변환이 됩니다.


그러나, Animal 클래스의 객체를 Dog 클래스의 형태로 바꾸려 하면 문제가 발생합니다. 당연하게도 Dog 클래스만이 가지고 있는 필드나 메소드가 있는데, Animal 클래스의 객체는 이를 가지고 있지 않기 때문입니다. 위 예제의 7행에 쓰인 코드는 애초에 animal이 Dog 클래스의 객체이기 때문에 컴파일 타임에도 런타임에도 오류가 발생하지 않는 것입니다.

class Animal {  }

class Dog : Animal {  }

Animal animal = new Animal();
Dog dog = (Dog)animal; // InvalidCastException 예외 발생!

런타임에 변환이 가능할 경우 별다른 오류 없이 변환이 되지만, 위 코드와 같이 변환이 불가능할 경우에는 실행 도중 InvalidCastException이란 예외가 발생합니다.


클래스의 상속편은 여기서 마치도록 하겠습니다. 수고하셨습니다.


다음 강좌에서는 확장 메소드, 중첩 클래스, 분할 클래스에 대해 배워보도록 하겠습니다.

  1. 김승현 at 2012.10.20 17:08 신고 [edit/del]

    감사합니다~

    Reply
  2. 을기 at 2012.12.10 20:27 신고 [edit/del]

    감사합니다 도움이되었어요!!

    Reply
  3. 염구나 at 2013.01.02 21:53 신고 [edit/del]

    감사합니다. 참고로 위 강좌 보시고 override와 new의 차이점이 구분이 안가시는 분들은 아래 URL보시기 바랍니다.

    http://msdn.microsoft.com/ko-kr/library/ms173153(v=vs.80).aspx

    간단히 얘기하자면 아래처럼 실행됩니다.

    Parent p = new Child();

    <override>
    - p.A(); 실행시 "자식 클래스(Child)의 A() 메서드 호출!" 출력
    <new>
    - p.A(); 실행시 "부모 클래스의 A() 메서드 호출!" 출력
    - 분명 child()로 생성했음에도 p의 타입이 Parent여서 Parent의 함수가 실행됨
    - Child c = new Child();일 때 p.A(); 실행시 "자식 클래스(Child)의 A() 메서드 호출!" 출력됨

    Reply
  4. ㅇㅇㅇ at 2013.07.22 14:36 신고 [edit/del]

    감사합니다. 덕분에 도움이 되었습니다 ^^

    Reply
  5. ZiNee at 2013.10.21 13:55 신고 [edit/del]

    많이 배웠습니다. 감사합니다. get, set 이 참으로 간결합니다.

    Reply
  6. iamksu at 2014.09.25 16:26 신고 [edit/del]

    1장부터 계속 정주행 중입니다. 잘보고있습니다.

    Reply
  7. SuaLove at 2014.10.21 16:58 신고 [edit/del]

    잘보고갑니다.

    Reply
  8. 카펠 at 2016.09.09 14:19 신고 [edit/del]

    감사합니다. C# 프로그래밍에 큰 도움 되었습니다.
    깔끔한 블로그네요!

    Reply
  9. williamgilbert at 2016.10.25 22:13 신고 [edit/del]

    덕분에 c#이 머리에 그려지는거 같네요.
    동영상 강의 듣고 있는데..
    동영상강의는 따라서 하면서 하니까 그때는 아는거 같은데 조금 지나면 잊어버려서요.
    글로 된거 보니까 정확하게 확립이 되는거 같네여.
    내일 되면....잊어버릴지도 모르지만 ㅎㅎㅎ;; 내일 와서 또 읽어볼께요.
    좋은 정보 감사합니다.

    Reply
  10. MyT at 2017.08.05 02:25 신고 [edit/del]

    언제나 잘 보고 있습니다.

    그런데 한가지 이상한게 보이죠? 23행을 보시면 자신을 가르키는 this 키워드가 사용되었습니다. this 키워드를 사용하여 부모 클래스의 멤버 변수에 접근은 할 수 있으나, 만약에 자식 클래스에도 num이라는 멤버 변수가 존재할때는 어떻게 접근하여야 할까요? 그러면 this 키워드가 아닌 base 키워드를 사용하시면 됩니다. 다음과 같이 말이죠.

    여기서 아래에서는 base에 대한 설명을 해주시는데 아무래도 문맥이 좀 꼬인 것 같습니다. 확인바랍니다~

    Reply
  11. 감사합니다. at 2017.08.18 19:55 신고 [edit/del]

    좋은 글 정말 감사합니다.

    Reply
  12. 김기용 at 2018.03.18 15:47 신고 [edit/del]

    잘 정리된 글 잘 보았습니다. 많은 도움이 되었어요~

    Reply
  13. jrady at 2018.05.26 11:03 신고 [edit/del]

    "new 지정자를 사용하면 부모 클래스의 멤버를 숨길 수 있게됩니다. " 라는 것의 의미를 잘 모르겠습니다.. 무슨 뜻인가요?

    Reply
  14. 감사합니다! at 2018.09.06 21:01 신고 [edit/del]

    덕분에 차근히 공부중입니다. 감사합니다.
    그런데, base 키워드를 통해 접근한 부모의 변수는 main에서 호출시에는 어떻게 해야 하나요??자녀 클래스에 있는 변수명과 동일하다 보니 뭔가 추가 키워드가 필요할 거 같은데 main안에서는 base 키워드를 이용하니, 정적 메서드에서 사용할 수 없다고 합니다ㅠ 물론 변수명을 동일하게 안가져 가면 되겠지만..알려주시면 감사하겠습니다!

    Reply
    • 덧붙 at 2018.09.07 00:19 신고 [edit/del]

      는 아래를 더 읽어보니, 이렇게 변수명이 서로 같으면 부모의 변수는 숨겨지기 때문에 main에서 접근할 수가 없어 지는 건가요?!!

submit


1. 생성자(Constructor)


오늘은 생성자와 소멸자에 대해서 알아보도록 하겠습니다. 생성자와 소멸자를 간단히 소개하자면, 생성자는 객체를 생성할 때 호출되는 메소드이며, 소멸자는 객체를 소멸시킬 때 호출되는 메소드라고 할 수 있습니다. 우선 생성자 부터 알아보고, 이 생성자가 어떻게 쓰이는지 아래 예제를 살펴보며 생각해봅시다. 그러기 전, 생성자의 선언 형식부터 잠깐 보고 들어가보도록 합시다.


생성자의 일반적인 선언 형식:

class 클래스명 {
    [접근 제한자] 클래스명(매개변수..)
    {
        //
    }

    ..
}

생성자의 선언 형식을 살펴보면, 생성자의 이름이 클래스의 이름과 똑같습니다. 그리고 이 생성자는 메소드와 같이 매개변수를 가질 수 있으며, 메소드와는 달리 특정 값을 반환할 수는 없고, 반환형도 가지고 있지 않습니다. 그리고 한가지, 여기서 드러나지는 않았지만 생성자는 객체 생성시 호출되는 메소드이며, 우리가 따로 생성자를 구현해주지 않아도 컴파일러에서 생성자를 직접 만들어줍니다. 즉, 우리가 생성자를 만들지 않아도 자동으로 기본 생성자(default constructor)가 생성된다는 것입니다. 이제 한번 생성자를 직접 다뤄봅시다

using System;

namespace ConsoleApplication2
{
    class Car
    {
        private int maxSpeed;
        private int speed = 0;
        private string model;

        public Car(int maxSpeed, string model)
        {
            this.maxSpeed = maxSpeed;
            this.model = model;
        }
        public void ShowCarInformation()
        {
            Console.WriteLine(model + "의 현재 속도: " + speed + "km/h, 최대 속도: " + maxSpeed + "km/h");
        }
        public void speedUp(int increment)
        {
            if (speed + increment > maxSpeed)
                Console.WriteLine("최대 속도 " + maxSpeed + "km/h를 넘길 수 없습니다.");
            else
            {
                speed += increment;
                Console.WriteLine(model + "의 현재 속도는 " + speed + "km/h 입니다.");
            }
        }
        public void speedDown(int decrement)
        {
            if (speed - decrement < 0)
                Console.WriteLine("속도는 0 아래로 떨어질 수 없습니다.");
            else
            {
                speed -= decrement;
                Console.WriteLine(model + "의 현재 속도는 " + speed + "km/h 입니다.");
            }
        }
    }
    class Program
    {
        static void Main(string[] args)
        {
            Car car = new Car(325, "람보르기니 가야르도");

            car.ShowCarInformation();
            car.speedUp(50);
            car.speedUp(40);
            car.speedUp(210);
            car.speedUp(30);
        }
    }
}

결과:

람보르기니 가야르도의 현재 속도: 0km/h, 최대 속도: 325km/h

람보르기니 가야르도의 현재 속도는 50km/h 입니다.

람보르기니 가야르도의 현재 속도는 90km/h 입니다.

람보르기니 가야르도의 현재 속도는 300km/h 입니다.

최대 속도 325km/h를 넘길 수 없습니다.

계속하려면 아무 키나 누르십시오 . . .


코드를 보시면서, 자세히 보셔야 할 부분은 11~15행 부분입니다. 이 부분에 생성자가 등장했으며, 매개변수로 정수형 값 하나와, 문자열 값 하나를 받는 부분이 있는 것을 확인할 수 있습니다. 매개변수 maxSpeed의 값을 멤버 변수 maxSpeed에 넣고, 매개변수 model의 값을 멤버 변수 model에 넣습니다.


그리고 45행을 보시면 객체를 생성하면서 325란 값과, 람보르기니 가야르도라는 문자열 값이 넘어갔습니다. 즉, 객체를 생성할 때 생성자가 호출되면서 멤버 변수를 초기화 하고 있습니다. 객체를 생성하는 부분을 아래와 같이 볼 수도 있겠죠?

클래스명 객체명 = new 생성자;

위 예제를 보시면, 생성자에는 일반적으로 객체를 생성하면서 멤버 변수를 자신이 원하는 값으로 초기화하는 부분이 들어갑니다. 한가지 더 알아두실게 있다면, 생성자도 오버로딩이 가능하다는 것입니다. 아래 예를 한번 보실까요?

using System;

namespace ConsoleApplication2
{
    class MyClass
    {
        public MyClass()
        {
            Console.WriteLine("매개변수가 없는 디폴트 생성자");
        }
        public MyClass(int a)
        {
            Console.WriteLine("정수형 매개변수");
        }
        public MyClass(double d)
        {
            Console.WriteLine("실수형 매개변수");
        }
    }

    class Program
    {
        static void Main(string[] args)
        {
            MyClass ma = new MyClass();
            MyClass mb = new MyClass(10);
            MyClass mc = new MyClass(25.5);
        }
    }
}

결과:

매개변수가 없는 디폴트 생성자

정수형 매개변수

실수형 매개변수

계속하려면 아무 키나 누르십시오 . . .


25~27행에서 ma, mb, mc라는 객체가 생성되면서 MyClass 생성자를 호출하죠? 넘어가는 매개변수의 형태와 수에 따라 이렇게 실행되는 영역을 바꿀 수 있습니다. 우리가 전에 배운 메소드 오버로딩 규칙이 그대로 적용된다고 보시면 됩니다. 이제는 소멸자에 대해서 알아보도록 할까요?


2. 소멸자(Destructor)


소멸자는 생성자와 달리 가비지 컬렉터(garbage collector)에 의해 객체가 소멸하는 시점을 판단하여 호출되는 메소드입니다. 여기서 가비지 컬렉터란, C#에서 효율적인 메모리 관리를 위해 가비지 컬렉터란 녀석이 자동으로 더는 사용되지 않는 객체를 수거해갑니다. (가비지 컬렉터에 대해서는 나중에 자세히 다룰 예정입니다.) 소멸자는, 생성자와는 달리 상속되거나 오버로드 될 수 없으며, 사용자가 호출할 수도 없습니다. 소멸자는 아래와 같은 형태로 쓰입니다. 


소멸자의 사용 형태:

class 클래스명 {
    ~클래스명()
    {
        //
    }
    ..
}

위처럼 보시는 바와 같이, 클래스의 이름 앞에 ~ 기호를 붙인게 바로 소멸자입니다. 아래는 소멸자에 관한 예제입니다.

using System;

namespace ConsoleApplication2
{
    class MyClass
    {
        private string name;

        public MyClass(string name)
        {
            this.name = name;
            Console.WriteLine(name + " 객체 생성!");
        }
        ~MyClass()
        {
            Console.WriteLine(name + " 객체 소멸!");
        }
    }

    class Program
    {
        static void Main(string[] args)
        {
            MyClass ma = new MyClass("A");
            MyClass mb = new MyClass("B");
            MyClass mc = new MyClass("C");
        }
    }
}

결과:

A 객체 생성!

B 객체 생성!

C 객체 생성!

C 객체 소멸!

B 객체 소멸!

A 객체 소멸!

계속하려면 아무 키나 누르십시오 . . .


14행을 보시면 소멸자가 등장했음을 알 수 있습니다. 결과를 보시면 a, b, c 객체가 만들어진 뒤에 소멸하는 것을 확인할 수 있습니다. 그런데 소멸되는 순서를 보시면 a, b, c가 아닌 c, b, a 순으로 소멸되고 있죠? 이것은 가비지 컬렉터가 언제 움직일지, 어떤 순서로 소멸시킬지 모르기 때문에, 실행할 때마다 소멸되는 순서는 바뀔 수 있습니다.


이런 소멸자는 가비지 컬렉터가 수거할 수 없는 자원이 있을 경우에 이를 해지할 때 사용합니다. 하지만 가급적, 소멸자를 사용하지 말라고 권하고 싶습니다. 객체의 소멸은 소멸자 없이도, 가비지 컬렉터가 처리할 수 있으며 소멸자가 굳이 필요하지 않은데도 불구하고 사용한다면 프로그램의 성능 저하만 유발시킬 수 있습니다.


오늘 강좌는 여기서 마치도록 하겠습니다. 수고하셨습니다.


다음 강좌에서는 클래스의 상속에 대해서 알아보도록 하겠습니다.

  1. 김승현 at 2012.10.20 16:46 신고 [edit/del]

    감사합니다~

    Reply
  2. UW at 2012.11.28 11:17 신고 [edit/del]

    생성자란 하나의 클래스에서 쓰던 정보들 (변수 등)을 다른 클래스에서도 사용하고 싶을때 정보를 호출하기
    위해 사용하는 메소드
    라고 이해해도 괜찬은건가요??
    생성자는 그냥 냅둬도 생긴다는데 굳이 만드는이유를 저거 말곤 모르겟어요ㅠㅠ

    Reply
    • BlogIcon EXYNOA at 2012.11.28 19:13 신고 [edit/del]

      생성자는 초기화 영역으로 생각해두시면 되겠습니다. 유용하게 쓰이는 녀석이죠. 생성자는.

    • 교강용 at 2013.03.13 12:35 신고 [edit/del]

      자동차 객체를 만들때 처음부터 기어가 3단인 상태로 생성하고 싶다면, 이걸 생성자에서 작업해주지 않는다면 귀찮아지겠죠. 즉, 객체의 속성을 원래의 기본값이 아닌 다른 값으로 주고 싶을때 쓸 수 있는 것입니다. 그 외에도 수많은 용도가 있지만요~

  3. ZiNee at 2013.10.15 11:37 신고 [edit/del]

    감사합니다 ^^*

    Reply
  4. jhKIM at 2016.01.05 22:25 신고 [edit/del]

    감사합니다...

    Reply

submit


1. 접근 제한자(Access Modifier)


오늘은 접근 범위를 결정하게 해주는 접근 제한자에 대해서 알아보려고 합니다. 접근 제한자에는 public, protected, internal, private가 있습니다. 이미 public라는 접근 제한자는 본적이 있죠? 이 네 가지의 접근 제한자에 대해 알아보려고 합니다. 아래는 4개의 접근 제한자를 정리해놓은 표입니다.


접근 제한자

설명 

private

클래스 내부에서만 접근이 가능합니다.

public 

모든 곳에서 해당 멤버로 접근이 가능합니다.

internal

같은 어셈블리에서만 public으로 접근이 가능합니다. 

protected

클래스 외부에서 접근할 수 없으나 파생 클래스에서는 접근이 가능합니다.

protected internal 

같은 어셈블리에서만 protected으로 접근이 가능합니다. 


이해를 돕기위해 예를 하나 들겠습니다. 예를 들어, 다음과 같은 클래스가 있다고 가정해봅시다.

class A

{

  int B;

  int C;

  ...

}

클래스 A 내에 B와 C라는 멤버 변수가 존재합니다. 그럼 이제 한번, A 클래스에 기반을 둔 객체를 생성하고 이 객체로 접근을 해보도록 하겠습니다.

A a = new A();

a.B = 1; // 보호 수준 에러!

그런데, 컴파일을 하자마자 에러가 발생했습니다. 에러를 확인해보니, 다음과 같은 이유로 컴파일을 할 수 없다는 것이였습니다.


오류 1 보호 수준 때문에 'ConsoleApplication1.A.B'에 액세스할 수 없습니다. C:\Users\su6net\AppData\Local\Temporary Projects\ConsoleApplication1\Program.cs 20 15 ConsoleApplication1


그 이유가 무엇일까요? 클래스의 멤버를 접근 제한자로 수식하지 않으면 멤버의 보호 수준(접근 수준)은 무조건 private로 자동으로 지정이 됩니다. private로 지정되면 클래스 내부에서만 접근이 가능하다는것은 이미 위의 표에서 보았습니다. 즉, 외부에서는 접근할 수 없으니 접근이 가능하게 멤버의 접근 수준을 public로 지정해봅시다.
class A
{
   public int B;
   public int C;
   ...
}
그랬더니, 더는 보호 수준 에러가 보이지 않았습니다. public으로 지정하게 되면, 모든 곳에서 이 멤버에 접근할 수 있게 되니 말이죠. 그럼 internal과 protected는 뭘까요? internal로 접근 수준이 지정되면 동일한 어셈블리, 즉 동일한 프로그램에서만 접근할 수 있습니다. 만약 어셈블리 외부에서 참조하게 되면 당연히 오류가 발생합니다.

protected는 클래스 외부에서는 접근할수 없지만 파생된 클래스에서 접근할 수 있는 특징을 가지고 있습니다.
namespace ConsoleApplication1
{
    class A
    {
        protected int x = 123;
    }

    class B : A
    {
        static void Main()
        {
            A a = new A();
            B b = new B();

            // 에러 CS1540 발생, 왜냐하면 X는 오직 A에서 파생된 클래스에서만 접근이 가능하기 때문
            a.x = 10;

            // A에서 파생된 클래스인 B에선 접근이 가능하다.
            b.x = 10;
        }
    }
}
위의 protected 관련 예제는 MSDN에 있던 예제를 가져온 것입니다. 5줄을 보시면 접근 수준이 protected로 지정되었죠? 이것을 컴파일하면 어떤일이 벌어질까요? 컴파일 시에는 다음과 같은 오류가 발생합니다.

오류 1 'ConsoleApplication1.A' 형식의 한정자를 통해 보호된 'ConsoleApplication1.A.x' 멤버에 액세스할 수 없습니다. 한정자는 'ConsoleApplication1.B' 형식이거나 여기에서 파생된 형식이어야 합니다. C:\Users\su6net\documents\visual studio 2010\Projects\ConsoleApplication1\ConsoleApplication1\Program.cs 21 15 ConsoleApplication1

오류를 살펴보자면 즉, 'A.x 멤버에 접근하려면 B 클래스이거나, B 클래스로부터 파생된 클래스여야 접근이 가능하다'는 것을 말하고 있습니다. 그렇다면 16줄에 있는 코드를 주석처리 하고 컴파일하게 되면, 아무런 오류없이 컴파일 되는것을 확인할 수 있습니다. 19줄에 있는 코드는 아무런 문제없이 접근이 가능하죠. 

파생 클래스에 대해서는 나중에 다시 한번 살펴볼 것인데, 우선은 한정자마다 접근할 수 있는 범위가 어떠한지만 기억해두시면 됩니다.

2. this

this 키워드는 자기 자신을 가리킬때 사용하는 키워드입니다. this 키워드가 어떠한 역할을 하는지 예제를 통해 살펴보도록 합시다.
using System;

namespace ConsoleApplication1
{
    class A
    {
        private int num;

        public A(int num)
        {
            num = num;
        }
        public void Show()
        {
            Console.WriteLine("num: " + num);
        }
    }

    class Program
    {
        static void Main(string[] args)
        {
            A a = new A(50);

            a.Show();
        }
    }
}
결과:
num: 0
계속하려면 아무 키나 누르십시오 . . .

코드를 보니 처음 접하는게 있죠? 바로 9~12행을 보니 클래스의 이름과 똑같은 이름을 가진 메소드가 보입니다. 이를 생성자라고 하며, 다음 강좌에서 소멸자와 같이 배우게 될 녀석입니다.

간단히 알아보자면, 생성자는 객체를 생성하기 위해 존재하며, 객체를 생성할 때 객체의 멤버 변수를 원하는 값으로 초기화 하고 싶을때 주로 사용됩니다. 객체를 생성할 때 생성자가 한번 호출되는데, 11행을 보시면 num을 num 값으로 초기화 시키고 있습니다. 말이 좀 애매한데, 여기선 객체의 멤버 변수인 num 값이 변한것이 아니라, 매개변수의 값이 변했다고 보시면 됩니다. 아무런 의미도 없는 코드죠. 23행에서 객체를 생성할때 생성자에게 50이란 값을 넘겨주었는데, 결과를 보시면 num의 값은 변하지 않습니다.

그렇다면, 매개변수 이름과 멤버 변수의 이름이 서로 같을때 멤버 변수 num의 값을 수정하려면 어떻게 해야 할까요? 바로 this 키워드를 사용하시면 됩니다. 아래와 같이 바꿔주시면 되겠죠?
...
public A(int num)
{
     this.num = num; 
}
...
여기서 this 키워드를 사용하면 클래스 내에 정의한 멤버 변수 num을 가르키게 됩니다. 즉, 클래스 내에 정의한 멤버 변수 num에 매개변수 num의 값을 집어넣으라는 말과 같습니다. 다시 결과를 보시면 정상적인 값을 출력하고 있음을 확인할 수 있습니다.

이번 강좌는 여기서 마무리 짓도록 하겠습니다. 수고하셨습니다.

다음 강좌에서는 생성자, 소멸자에 대해서 배워보도록 하겠습니다.

  1. 김승현 at 2012.10.20 13:47 신고 [edit/del]

    감사합니다~

    Reply
  2. 질문 at 2012.11.05 17:49 신고 [edit/del]

    안녕하세요, 시간날때마다 들어와서 배우고 갑니다.
    위에 보면 Class B 안의 main()에서 B b = new B(); 로 다시 B를 생성하는데요,
    B를 정의해서 만들고 있는데, 그 안에서 B를 생성할 수 있다는게 이해가 안되네요.
    자세한 설명 부탁드려요.

    Reply
    • BlogIcon EXYNOA at 2012.11.06 19:03 신고 [edit/del]

      그 클래스가 존재하기만 한다면, 그 클래스의 객체를 어디서나 만들 수 있습니다.

      이는 C#에서 지원하는 부분이며, 만약 B 클래스 내에서 B 객체를 만들 경우에는 그 B 객체가 만들어진 메소드를 제외한 나머지 메소드나 필드는 그 객체로 접근할 수 있습니다.

  3. ZiNee at 2013.10.15 11:11 신고 [edit/del]

    아직까진 이해 잘 됩니다. 계속해서 정주행 하겠습니다.

    Reply
  4. GB at 2018.02.06 15:42 신고 [edit/del]

    C나 C++ 관련 기본지식 없이 바로 C#공부중인데 몇번씩 보면 하나 이해가 되고 그러네요~ 잘보고 있습니다.

    Reply
  5. 최동준 at 2018.03.20 15:26 신고 [edit/del]

    대단하십니다.
    정말 이해되기 쉽게 해 놓으셨군요
    존경스럽습니다.
    감사합니다.

    Reply
  6. MGV at 2018.03.21 09:48 신고 [edit/del]

    갑자기 클래스 상속이 나와서 당황했네요. 13을 보고 다시 봐야겠어요~

    Reply

submit


1. 객체(Object)


이번 강좌에서는 강력한 기능을 제공하는 클래스에 대해서 배우기 전에, 객체 지향 프로그래밍(Object Oriented Programming, OOP)에 대해 알고 넘어가야 하기에 객체 지향 프로그래밍이 도대체 무엇인지에 대해서 설명을 해드리도록 하겠습니다. 우리가 알고있듯 C#은 객체 지향 언어입니다. C#에서의 객체(Object)는 핵심 중에 핵심이라 말할 수 있죠.


오로지 객체만을 다루는 책이 있을정도로 객체를 완벽히 이해하기에는 많은 어려움이 있습니다.


우리 주위에서 객체는 쉽게 찾아볼 수 있습니다. 눈에 보이는 모든 것이 객체가 될 수 있습니다. 지금 앞에 보이는 모니터, 마우스, 키보드, 프린터, 달력, 가방, 자동차 등과 같이 개념상으로 존재하는 것은 모두가 객체가 됩니다. 심지어 생각, 날씨 정보 등도 객체가 될 수 있습니다.



객체들은 각각 상태(state)와 행동(behavior)을 지니고 있습니다. 실세계의 모든 것은 상태와 행동으로 표현할 수 있죠. 전화기를 예로 들자면, 전화기의 색은 상태가 되고 전화기로 전화를 걸거나, 전화를 끊거나 하는 등의 동작은 행동이 됩니다. 또, 자동차를 예로 들자면 자동차의 속도 등이 상태가 될 수 있고, 속도를 올리거나, 속도를 내리거나, 브레이크를 밟거나 하는 등의 동작은 행동이 됩니다.


C# 프로그래밍에선 상태는 데이터(변수)로, 행동은 메소드로 표현할 수 있습니다. 즉, 객체는 데이터와 메소드로 이루어진다는 말과 같습니다. 쉽게 이해하기 위하여 흔히 드는 예를 하나 들겠습니다. 붕어빵이라는 객체를 만들기 위해서는 붕어빵을 찍어낼 틀이 필요합니다. 여기서 붕어빵을 찍어낼 틀은 클래스라고 할 수 있습니다. 붕어빵 틀(클래스)은 하나만 있어도 붕어빵(객체)를 여러 개 찍어낼 수 있죠. 이해되시죠?


2. 클래스(Class)


이제는 우리가 직접 클래스를 한번 만들어보도록 하겠습니다. 클래스는 아래와 같이 선언할 수 있습니다.

[접근 제한자] class 클래스명
{
    // 필드, 메소드 ...
}

위의 예제에서 보이는 접근 제한자는 말 그대로 해당 클래스로의 접근을 제한하고자 할 때 넣는 공간입니다. 접근 제한자에 대해서는 클래스를 배운 뒤에 자세히 다루도록 하겠습니다. 그리고 클래스 내에선 필드, 메소드가 있는데 상태를 필드(field)로 나타내고, 행동을 메소드(method)로 가진다고 생각하시면 됩니다.


클래스에 대한 이해를 돕기위해, 개를 추상화하여 클래스로 만들어 보도록 하겠습니다. 여기서 추상화란, 불필요한 부분은 과감히 없애버리고 중요한 부분에만 중점을 두어 간략화 시킨 것을 말합니다. 아래는 개를 추상화하여 클래스로 선언한 예제입니다.

..
class Dog {
    private string name; // 개의 이름을 나타내는 필드
    private string gender; // 개의 성별을 나타내는 필드
    private string ownernames; // 개의 주인 이름을 나타내는 필드

    public void Bark() // 짖는 행동
    {
        Console.WriteLine("{0} : 멍멍!", name);
    }
}
...

아래는 위의 코드에서 덧붙여 완성시킨 예제입니다.

using System;

namespace ConsoleApplication1
{
    class Dog
    {
        public string name; // 개의 이름을 나타내는 필드
        public string gender; // 개의 성별을 나타내는 필드
        public string ownernames; // 개의 주인 이름을 나타내는 필드

        public void Bark() // 짖는 행동
        {
            Console.WriteLine("{0} : 멍멍!", name);
        }
    }

    class Program
    {
        static void Main(string[] args)
        {
            Dog dog = new Dog();
            dog.name = "칸";
            dog.gender = "남";
            dog.ownernames = "철수";

            dog.Bark();

            Console.WriteLine("개의 이름: {0}, 개의 성별: {1}, 개의 주인이름: {2}", dog.name, dog.gender, dog.ownernames);
        }
    }
}

결과:

칸 : 멍멍!

개의 이름: 칸, 개의 성별: 남, 개의 주인이름: 철수

계속하려면 아무 키나 누르십시오 . . .


코드를 보시면 5행에서 Dog라는 클래스가 등장합니다. 이 클래스 안을 살펴보면 개의 이름을 나타내는 필드, 성별을 나타내는 필드, 주인 이름을 나타내는 필드와 짖는 행동을 취하는 메소드가 존재합니다. 24행을 보시면 Dog라는 틀로 dog라는 객체를 만들고 있습니다. 25~28에서 멤버 변수(필드와 같은 말)의 값을 초기화시키고 있습니다. 그런 뒤에 dog 객체 내에 있는 Bark라는 메소드를 호출하고 있습니다. 그 후에는 개의 이름, 개의 성별, 개의 주인이름을 가져와 출력시키고 프로그램이 종료됩니다.


이 코드중에서 주목하셔야 할 부분은 바로 21행의 코드입니다.

Dog dog = new Dog();

위 코드는 new 키워드 뒤에 등장하는 Dog 클래스를 기반으로 하여 dog라는 객체를 만듭니다. 마지막에 등장하는 Dog()는 생성자라고 하는데, 생성자는 접근 제한자, 소멸자, this와 같이 다음 강좌에서 배워보도록 하겠습니다. 아래는 new 키워드를 이용한 객체의 생성 방법입니다.

클래스명 식별자 = new 클래스명();

지금까지, 클래스의 선언 방법과 객체의 생성 방법, 멤버 변수에 접근하는 등 여러 가지를 알아보았습니다. 클래스는 여기가 끝이 아니라 지금부터 시작임을 명심하셔야 합니다. 여기까지 보느라 수고하셨고, 다음 강좌에서는 접근 제한자, this에 대해 알아보도록 하겠습니다.

  1. C# at 2012.10.18 17:07 신고 [edit/del]

    강의 감사합니다 ㅎㅎ

    Reply
  2. 김승현 at 2012.10.20 13:25 신고 [edit/del]

    감사합니다~

    Reply
  3. UW at 2012.11.27 18:55 신고 [edit/del]

    질문이 있는데요
    왜 굳이 클래스를 여러개 만드는거죠?
    위에 예제를 보면 메소드를 여러개 만들면 클래스를 굳이 더 만들지않아도 충분히 원하는 결과를 얻을수잇을거같은데...
    제가 잘 모르는건가요ㅠㅠ 아직 클래스에 대해 제대로 이해못한거같아요

    Reply
    • BlogIcon EXYNOA at 2012.11.27 19:15 신고 [edit/del]

      지금은 필요없다고 느껴지실지도 모르겠지만, 규모가 큰 프로젝트를 진행하다 보면 기능을 추가하게 될때 클래스의 도움을 받지 않고 오로지 메서드로, 배열로 채워버린 경우 무엇하나 건들이면 프로그램 전체가 꼬여버릴 수 있습니다.

      클래스를 사용하면, 프로그램의 관리가 용이하고 유지보수시에 상당히 큰 도움이 되는 녀석입니다. 그리고 클래스의 특징을 좀 더 살펴보시면 '다형성'이라는 녀석이 있는데, 한번 클래스를 만들고 사용하다 보시면 알게 되실거에요.

    • UW at 2012.11.28 11:00 신고 [edit/del]

      아...편리성떄문이 가장 큰 이유인건가요?

    • BlogIcon EXYNOA at 2012.11.28 19:11 신고 [edit/del]

      꼭 편리성뿐만은 아닙니다. 구조적 프로그래밍을 이해하시면 클래스를 왜 사용하게 되는지, 조금이라도 아시게 될 것 같네요.

  4. KkK at 2013.08.03 19:19 신고 [edit/del]

    window 응용 프로그램에서 예제 연습하려면 어떤식으로 예제 적용해야 하나요 ?

    Reply
    • BlogIcon EXYNOA at 2013.08.04 01:18 신고 [edit/del]

      콘솔 환경에서 적응하시다가, 어느정도 개념을 익히신 정도면 MSDN 참고하시면서 윈도우 폼 환경에서 진행해보세요.

      MSDN가 설명이 난해하거나 오역때문에 이해가 안되는 경우는, MSDN 말고도 윈도우 폼을 설명해둔 게시글들이 많이 있으니 한번 검색하셔서 찾아보세요.

    • KkK at 2013.08.05 10:44 신고 [edit/del]

      네 감사합니다~

  5. ZiNee at 2013.10.15 10:49 신고 [edit/del]

    드디어 객체지향 강좌까지 왔네요. OOP를 단 10% 만이라도 이해할 수 있다면 좋겠어요. 좋은 강좌 감사합니다.

    Reply
  6. BlogIcon L0GIC at 2013.12.08 17:10 신고 [edit/del]

    오호, 멤버와 필드가 같은 의미인거 처음알아가네요.

    좋은 강좌 감사합니다.

    Reply
  7. PohnNoies at 2013.12.09 10:22 신고 [edit/del]

    음... Class가 객체가 되는건지 Namespace가 객체가 되는건지 햇갈리네요..
    Class를 구성하는것은 Method와 Value이고.
    Namespace를 구성하는것은 Class...
    여기서 Namespace는 그냥 공간만 할당해주는것이지.. 객체라고 따로 지정되지않는건가요..?
    Namespace는 항상 만들어지면서도 기능을 잘 모르겠어요..

    Reply
    • BlogIcon EXYNOA at 2013.12.11 18:46 신고 [edit/del]

      클래스는 객체가 아니라 객체를 만들어내는 틀일 뿐이고, 네임스페이스는 논리적 그룹으로 나누는 것입니다. 저도 지식이 미약하여 자세한 설명을 못해드리는 점은 죄송합니다 (_ _)..

    • oz at 2015.08.04 14:34 신고 [edit/del]

      namespace 는 클래스가 속해 있는 주소 개념으로 보면 됩니다.
      자바에서의 package 의 개념과 비슷하죠 (맞죠? ㅎㅎ)

      언제 유용하냐하면,
      이 세상에 C#으로 정의된 클래스가 1억개 정도 있다고 치면,
      이중에 똑같은 이름의 클래스가 없다고 보장할 수 있을까요?
      단연코 없습니다.
      그래서 namespace 로 묶어 두면 편하게 분류해서 해당 클래스를 가져올 수 있죠.

  8. peter at 2014.09.08 07:51 신고 [edit/del]

    public void bark()는 왜 뒤에 세미콜론이 붙지 않는 이유가 궁금해요?
    그리고,뒤에 ()만 있을 때의 기능이 궁금해요...

    좋은 강의 감사합니다.

    Reply
    • oz at 2015.08.04 14:30 신고 [edit/del]

      함수를 사용할 땐 뒤에 ; 을 붙이지만
      구현(define) 할땐 붙이지 않습니다.

      C/C++ 에서는 정의(declare)할땐 붙입니다만...

  9. MT at 2017.07.31 15:24 신고 [edit/del]

    언제나 강좌 잘 보고 있습니다.
    위에 Dog 클래스에서 접근자를 private로 설정하셨는데 아래에 new Class 예문에서는 public으로 접근자가 변경되었는데 위에서 복붙해서 자기가 덛붙이려고 시도한 학생들은 왜 안되는지 고민할 가능성이 있을 것 같아요. 추가설명 또는 수정이 있으면 좋을 것 같습니다!(위에 접근자 공부하다가 다시 돌아와서 비교해보니 보여서 달아봤습니다. 지금 컴파일러를 켜볼 수 없는 환경이라, 혹시 제가 잘못 이해하고 있는거면 리플 삭제하겠습니다. ㅠ..)

    Reply
  10. hoi at 2018.05.30 16:18 신고 [edit/del]

    저... 강의를 쭉 봐오다가 궁금한 점이 생겨서 질문 드립니다.
    Dog라는 Class를 만들어서 메인 프로그램에서 쓰는데
    그렇다면 class가 구조체랑 같은건 가요 ? 가져다가 쓰는 방식이 비슷한거 같아서요
    C 계열은 처음이라 강의 잘 보고 있었는데 이부분은 좀 아리송한거 같아서 질문 드려요 ㅠㅠ

    Reply

submit

티스토리 툴바