[C# 강좌] C# 프로그래밍 #09- 상속

프로그램&DB/C# 2009. 4. 8. 10:27 Posted by Josep.H.S

3.2 상속

3.2.1 상속이란?

상속이란 클래스를 만들 때, 이전에 만들어진 클래스의 기능을 가져와 클래스를 구성하거나 사용하기 위해 사용되는 객체지향프로그래밍 기법의 하나이다. C#에서 상속은 C++와는 약간의 차이가 있는데, 그 중 하나는 클래스 다중 상속을 지원하지 않는 다는 것이다.

승용차, SUV차량 2개의 자동차에 대한 프로그램을 만든다고 가정하자. 2가지 클래스의 공통점은 자동차라는 것이다. 또한, 기능적인 면에서 거의 일치하며, 다만 약간씩의 차이가 있을 뿐이다. 이것을 어떻게 구현할 것인가?

유사한 기능들을 승용차에 구현하고 또 SUV차량에 구현하는 것은 구조적으로 좋지 못하다.

1) 기능을 업데이트 할 때 2가지 차량에 대한 모든 기능을 수정해야 한다.

2) 차종이 많아지면 차종에 따른 클래스를 모두 만들어야 한다.

 

상속은 클래스 이름 뒤에 콜론(:)을 붙이고 상속 받을 부모 클래스명을 넣게 된다.

/액세스 한정자/  class [자식 클래스 이름] : [부모 클래스 이름]

 

다음 예제는 상속이라는 방법을 통해 차량의 기능을 구현해 보도록 하겠다.

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

32

33

34

35

36

37

38

39

40

41

42

43

44

45

46

47

48

49

50

51

52

53

54

55

56

57

58

59

60

61

62

63

namespace Inheritance_Sample1

{

    class Program

    {

        static void Main(string[] args)

        {

            Automobile myCar1 = new Automobile();

            Console.WriteLine(myCar1.GetName());

            myCar1.doStart();

            myCar1.doStop();

 

            SUV myCar2 = new SUV();

            Console.WriteLine(myCar2.GetName());

            myCar2.doStart();

            myCar2.doStop();

 

            Console.ReadLine();

        }

    }

 

    public class Automobile : Car

    {

        public Automobile()

        {

            CarName = "승용차";

        }

    }

 

    public class SUV : Car

    {

        public SUV()

        {

            CarName = "SUV";

        }

    }

 

    public class Car

    {

        protected bool IsEngineOn;

        protected bool EngineBreak;

        protected int SteeringWheel;

        protected string CarName = "";

 

        public Car()

        {

        }

 

        public void doStart()

        {

            Console.WriteLine("출발");

        }

 

        public void doStop()

        {

            Console.WriteLine("정지");

        }

 

        public string GetName()

        {

            return CarName;

        }

    }

}

 

21번째 줄의 승용차(Automobile) 29번째 줄의 SUV차량은 모두 Car로부터 상속을 받고 있다. 따라서 승용차와 SUV 차량은 모두 Car의 멤버 변수와 doStart, doStop의 멤버 메서드를 그대로 가진다고 보면 된다. 실제 승용차 클래스와 SUV 클래스는 생성자에서 CarName 변수 하나만을 셋팅하고 있지만, Main 메서드에서 보면 둘다 doStart doStop 메서드를 이용하는 것을 볼 수 있다. 이와 같이 부모 클래스의 멤버 변수와 멤버 메서드를 활용 가능하도록 해주는 것을 상속이라고 보면 된다.

상속은 받은 자식 클래스는 부모 클래스의 멤버들의 접근 권한에 따라 부모 클래스에 대해 접근이 가능하게 된다.

 

3.2.2 생성자 재정의

 

 

3.2.3 상속과 재정의(Override)

클래스 상속 시에 기존에 정의된 부모 클래스의 작동이 현재 클래스에서는 달라져야 하거나, 부모 클래스가 추상 클래스인 경우 멤버 메서드들에 대한 재정의를 해야 한다. 이를 메서드 재정의(override)라고 부른다. 기존에 부모 클래스의 메서드에 대해 재정의 한다고 명시적으로 알려주기 위해 override 키워드를 이용한다.

 

1) 재정의 가능한 메서드

재정의 가능한 메서드는 virtual 로 선언되어 있거나, abstract로 선언되어 있는 경우 자식 클래스에서 재정의 가능하다. Virtual abstract의 차이는 abstract의 경우 자식 클래스에서 무조건 재정의를 해주어야 하지만, virtual의 경우는 필요할 경우 재정의를 해줄 수 있다.

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

32

33

34

35

36

37

38

39

40

41

42

43

namespace Override_Sample1

{

    class Program

    {

        static void Main(string[] args)

        {

            MyCar car = new MyCar();

            car.doStart();

            car.doStop();

 

            Console.ReadLine();

        }

    }

 

    public class MyCar : Car

    {

        public void doStop()

        {

base.doStop();

            Console.WriteLine("Override doStop");

        }

    }

 

    public class Car

    {

        protected bool IsEngineOn;

        protected bool EngineBreak;

        protected int SteeringWheel;

 

        public Car()

        {

        }

 

        public virtual void doStart()

        {

            Console.WriteLine("doStart");

        }

 

        public virtual void doStop()

        {

            Console.WriteLine("Override doStop");

        }

    }

}

 

위의 소스에서 Car 클래스의 doStart doStop메서드는 virtual로 선언되었다. 그리고, 15번째 줄의 MyCar 클래스에서 doStop가 재정의되고 있는 것을 볼 수 있다. 이와 같이 virtual 로 선언된 경우 재정의 할 수 있고 하지 않아도 아무런 문제가 없다.

부모 클래스가 abstract이고 abstract인 메서드가 존재할 경우 상속 받는 자식 클래스는 abstract 메서드를 무조건 재정의 해주어야 한다.(추상 클래스 참조)

 

2) override를 통한 재정의와 new를 통한 재정의

 

 

 

 

3.2.4 메서드 오버로딩(Overloading)

메서드는 메서드 이름과 매개변수 목록 반환 형식으로 이루어진다. 메서드 오버로딩은 동일한 이름의 메서드에 매개 변수를 다르게 하여 여러 개의 메서드로 정의하는 것을 의미한다.

메서드를 오버로딩 하는 이유는 주로 메서드에 여러 타입의 데이터를 처리할 수 있도록 하기 위해 이용된다. 다음 소스를 보자.

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

32

33

namespace Inheritance_Sample1

{

    class Program

    {

        static void Main(string[] args)

        {

            char num1 = '1';

            string num2 = "123";

 

            if (new Program().IsDigit(num1)) Console.WriteLine("숫자");

            else Console.WriteLine("숫자 아님");

 

            if (new Program().IsDigit(num2)) Console.WriteLine("숫자");

            else Console.WriteLine("숫자 아님");

 

            Console.ReadLine();

        }

 

        public bool IsDigit(char target)

        {

            return Char.IsDigit(target);

        }

 

        public bool IsDigit(string target)

        {

            for (int i = 0; i < target.Length; i++)

            {

                if (IsDigit(target[i])) continue;

                else return false;

            }

return true;

        }

    }

}

 

이 소스에는 IsDigit라는 이름의 메서드가 매개 변수 타입이 하나는 char(19번째 줄)이고 하나는 string(24번째 줄)으로 2개 선언되어 있다. 이와 같이 메서드 이름이 동일하며, 매개 변수만 다른 경우를 메서드 오버로딩이라고 부른다는 걸 위에서 설명하였다. 여기에 이용된 IsDigit 메서드는 입력된 값이 숫자인지 판별해주는 메서드로, 2개를 만든 이유는 문자로 입력된 매개 변수와 문자열로 입력된 매개 변수를 둘 다 처리해줄 수 있도록 하기 위해서이다.

24번째 줄의 IsDigit 메서드의 매개변수를 char 형으로 수정하게 되면 동일한 매개 변수 형식을 가지는 멤버가 미리 정의되어 있다고 컴파일 오류가 발생하게 된다.

 

3.2.6 추상 클래스

추상 클래스는 클래스를 설계하는데 있어서 기본 개념만을 담고 있는 미완성의 클래스라고 볼 수 있다. 추상 클래스는 일반 클래스 작성과 마찬가지로 멤버를 정의하고 메서드를 만들게 되지만, 추상 클래스만의 특징은 구현을 가지지 않는 추상 메서드를 가질 수 있다는 것이다. 그럼 왜 추상 메서드를 가지는 추상 클래스를 굳이 만들어 사용하는가?

클래스라는 개념이 들어가 있는 이상, 데이터 추상화와 상속이라는 개념을 따로 때어 놓고는 갈수가 없을 것이다. 추상 클래스를 사용하는 이유는 메서드 추상화와 자식 클래스들에 대한 메서드 구현을 강요하기 위한 의미로 보면 된다.

경유차, 가솔린차, LPG가스차 라는 3가지의 자동차 클래스가 있을 때, 시동을 건다던지, 브레이크를 밟는다던지, 라이트를 켠다던지와 같은 몇 가지 동일한 행위를 하는 역할을 가정해 보자. 이 기능들을 처리하기 위해 각각의 클래스에서 구현하기 보다는 Car이라는 클래스를 만들어 기능들에 대한 메서드를 구현하고 이를 상속 받는 클래스 경유차, 가솔린차, LPG가스차를 만들어 사용하면 부모 클래스의 메서드를 가져와 그대로 이용할 수도 있다. 그러나 동일한 행위를 하지만 내부적으로 그 행위를 하기 위해 다른 행위들을 해야 하는 경우가 있을 수 있다. 여기서는 엔진에 공급하는 연료가 다 다르므로 처리가 틀려져야 할 수 있다. 이 경우 행위에 대한 이름만 정의하고 각각 상속받은 클래스에서 구현해야 한다. 이때 사용하는 것이 추상 메서드라는 것이다. 사용자에게 이 부분은 따로 구현해야 한다는 것을 강제할 수 있다는 것이다.

추상 클래스는 abstract 키워드를 이용해서 만들 수 있으며 그 형식은 다음과 같다.

/액세스 한정자/  abstract class [클래스 이름]

 

abstract 한정자를 이용해 클래스를 구현하게 되면, 그 클래스는 인스턴스화 해서 사용할 수 없고 그 클래스를 상속받아 구현해서 이용해야 한다.

 

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

32

33

34

35

36

37

38

39

40

41

42

43

44

45

using System;

using System.Collections.Generic;

using System.Text;

 

namespace Method_Sample1

{

    class Program

    {       

        static void Main(string[] args)

        {

            LPGCar lpgCar = new LPGCar();

           

            Console.ReadLine();

        }

    }

 

    public class LPGCar : Car

    {

 

        public override void doStart()

        {

            this.IsEngineOn = true;

        }

 

        public override void doStop()

        {

            this.IsEngineOn = false;

        }

    }

 

    public abstract class Car

    {

        protected bool IsEngineOn;

        protected bool EngineBreak;

        protected int SteeringWheel;

       

        public Car()

        {

        }

 

        public abstract void doStart();

        public abstract void doStop();

       

    }

}

 

 

3.2.5 인터페이스 상속

인터페이스는 구현을 갖지 않는 껍데기를 만들기 위해 이용된다. C# C++의 다중 클래스 상속과 같은 기능을 지원하지 않는다. 상속은 오직 클래스 하나만을 상속 받을 수 밖에 없다. 따라서, 여러 가지 특성을 같이 가져야 하는 클래스에 대해서는 각각의 특성의 종류를 묶어 인터페이스들을 만들고 다중 인터페이스 상속을 이용할 수 있다. 다음 소스는 인터페이스 상속을 통해 Excavator(굴삭기) 클래스에 대한 구현을 해 본 예제이다.

 

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

32

33

34

35

36

37

38

39

40

41

42

43

44

45

46

47

48

49

50

51

52

namespace Interface_Sample1

{

    public class Excavator : Car, ICrane, IBulldozer

    {

        public override void doStart()

        {

            this.IsEngineOn = true;

        }

 

        public override void doStop()

        {

            this.IsEngineOn = false;

        }

 

        public void doLift()

        {

        }

 

        public void doBulldoze()

        {

        }

    }

 

    public class Car

    {

        protected bool IsEngineOn;

        protected bool EngineBreak;

        protected int SteeringWheel;

 

        public Car()

        {

        }

 

        public virtual void doStart()

        {

        }

 

        public virtual void doStop()

        {

        }

    }

 

    public interface ICrane

    {

        void doLift();

    }

 

    public interface IBulldozer

    {

        void doBulldoze();

    }

}

 

 

다음 예제는 인터페이스 상속을 통해 구현된 클래스들을 인터페이스 타입으로 조건에 따라 불러오는 예제이다.

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

32

33

34

35

36

37

38

39

40

41

42

43

44

45

46

47

48

namespace Interface_Sample2

{

    class Program

    {

        static void Main(string[] args)

        {

            string _line = System.Console.ReadLine();

 

            IGame myGame;

            if (_line == "1") myGame = new Card();

            else if (_line == "2") myGame = new Gostop();

            else myGame = new DifImage();

 

            Console.WriteLine(myGame.SelectGame());

 

            Console.Read();

        }

    }

 

    public class Card : IGame

    {

        public string SelectGame()

        {

            return "카드";

        }

    }

 

    public class Gostop : IGame

    {

        public string SelectGame()

        {

            return "고스톱";

        }

    }

 

    public class DifImage : IGame

    {

        public string SelectGame()

        {

            return "숨은그림찾기";

        }

    }

 

    public interface IGame

    {

        string SelectGame();

    }

}

 



/* 출처 */
inobae의 놀이터
http://blog.naver.com/inobae

3. 객체 지행 프로그래밍(Object Oriented Programming)

3.1 클래스

3.1.1 C# 프로그램 구조

C# 프로그램은 클래스를 기본 요소로 모든 수행코드가 클래스 내에서 이루어진다. 다음 소스를 보자.

1

2

3

4

5

6

class Program

{

    static void Main(string[] args)

    {

    }

}

 

실제 using 구문과 namespace에 대한 정의 없이 위와 같이만 코드를 쳐도 프로그램이 정상적으로 컴파일 됨을 알 수 있다. 이는 이것만으로도 완벽한 프로그램이 된다는 것을 의미한다. 실제 C#의 응용프로그램은 위와 같이 구성된다. 1번째 라인과 같이 클래스가 하나 만들어지고, 3번째 라인과 같이 실제 프로그램의 진입점인 Main 메서드를 구현하게 된다. 다시 말해 C#에서 프로그램은 Main 메서드에서 시작되고 Main 메서드가 종료됨으로서 프로그램이 종료된다는 것이다. Main 메서드는 string 타입의 배열 args를 가지며, 이는 실행시 입력 받는 외부 파라메터가 된다. 윈도우 응용 프로그램 역시 이와 동일하게 Main 메서드에서 시작하게 된다는 것만 기억하고 클래스에 대해 알아보기로 하겠다.

실제 class 외부에서 선언되고 이용될 수 있는 코드는 클래스와 구조체, 열겨형 멤버 선언과 같이 데이터 구조를 담는 개체밖에는 선언되지 못한다는 것을 알고 넘어가면 된다.

 

3.1.2 클래스

클래스는 C#의 가장 기본적인 데이터 구조이다. 앞에서 보여준 C# 프로그램 구조 소스를 보면 Program 앞에 class라는 키워드로 시작하는 것을 볼 수 있다. 이는 Program이라는 이름을 이용하는 클래스를 선언한다는 것을 의미한다.

C#에서 클래스는 class라는 키워드를 클래스 이름 앞에 붙여 사용하게 된다. 또한, 프로그램 구조상 클래스에서 클래스를 이용하여 프로그램을 만드는 구조를 가진다. 클래스는 다음과 같은 형식으로 선언된다.

/액세스 한정자/ class [클래스 이름]

클래스의 엑세스 한정자 public, private, protected, internal, protected internal 등이 이용되며 이에 대한 설명은 3.1.2.2의 액세스 한정자에서 다시 다루도록 하겠다. C#에서 액세스 한정자를 생략하고 클래스를 만들 경우 기본으로 public 형으로 클래스가 만들어진다.

 

클래스를 이용하는 목적은 개체의 데이터를 추상화하고 특성과 역할을 정의하기 위해 이용된다. 또한 이는 상속이라는 방법을 통해 확장될 수 있다. 자동차를 추상화하는 클래스를 만들어 보기로 하자. 우선 자동차는 엔진, 브레이크, 핸들 등을 가진다고 가정하고 이 자동차의 기능은 출발과 정지가 있다고 가정하자. 여기서 일반적 구성요소는 엔진, 브레이크, 핸들이 되고, 가능한 행위는 출발과 정지로 정의된다는 것을 알 수 있다. 이를 클래스 다이어그램으로 그리면 아래와 같다.

 

실제 이 클래스를 코드로 구현해 보면 다음과 같다.

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

public class Car

{

    private string Engine;

    private bool EngineBreak;

    private int SteeringWheel;

 

    public Car()

    {

    }

 

    public void doStart()

    {

    }

 

    public void doStop()

    {

    }

}

 

Car라는 클래스를 먼저 만들고 구성 요소를 3, 4, 5번째 줄에 Engine, EngineBreak, SteeringWheel과 같이 선언했다. 기능에 대한 정의를 보면 출발은 doStart라고 11번째 줄에 정의했고, 정지는 15번째 줄에 정의해 놓았다.

이는 간단한 클래스를 정의하고 구현하는 기본적인 과정을 보여주고 있다.

 

 

3.1.2.1 클래스 멤버

클래스는 멤버 변수와 멤버 메서드를 포함한다.

1) 생성자

생성자는 개체 인스턴스를 만들 때 불려지는 메서드를 말한다. 생성자의 호출은 new 키워드를 이용하여 불려지며, 클래스를 초기화하는 역할을 수행한다. 생성자는 한번만 불려지며 생성자의 이름은 클래스의 이름과 동일하게 만들어야 한다. 또한, 생성자는 목적에 따라 여러가지 형식을 이용할 수 있도록 오버로드 될 수 있다.

1

2

3

4

5

6

7

8

9

10

public class MyClass

{

        public MyClass()

        {

        }

 

        public MyClass(string myString)

        {

        }

}

 

3번째 줄은 파라메터를 갖지 않는 생성자를 구현한 것이며, 7번째 줄은 문자열을 파라메터로 갖는 생성자를 구현한 것이다. 위의 3번째 줄과 7번째 줄에서와 같이 생성자는  오버로드 되어 구현될 수 있으며, 일반적으로 개체를 초기화하는 코드가 들어가게 된다.

일반적으로 클래스는 생성자를 통해 생성되지 않으면 사용할 수 없다.(static 멤버 메서드의 경우 생성자 없이 사용 가능)

 

2) 소멸자

소멸자는 클래스 인스턴스를 소멸하는데 이용된며, 사용자에 의해 호출될 수 없다. 소멸자는 다음과 같이 구현한다.

1

2

3

4

5

6

7

public class MyClass

{

    ~MyClass()

    {

// 명시적인 개체 해제 구문

    }

}

 

3번째 라인은 MyClass라는 클래스의 소멸자를 명시적으로 구현한 예이다. 소멸자는 내부적으로 Finalize 메서드를 호출하게 된다.

소멸자가 포함된 클래스는 Finalize 큐에 올라가게 되고, 소멸자가 호출되면 큐를 처리하기 위한 가비지 수집기가 호출되게 된다.

 

-Dispose 메서드

Dispose 메서드는 IDispose 인터페이스를 구현하는 클래스에 구현된다. Dispose의 역할은 명시적으로 리소스를 해제하는데 이용된다. 가비지 수집기에 의해 리소스가 해제 되길 기다려도 되지만, 응용 프로그램에 큰 부담을 주는 외부 리소스를 이용하는 경우 명시적인 해제를 통해 시스템의 부담을 덜어 줄 수 있다.

 

3) 멤버 변수

C#은 모든 형식들이 개체로 만들어져 제공되며, 개체의 인스턴스로 이용될 수 있다. 멤버 변수란, 클래스에 전역적으로 선언된 변수들을 뜻한다. 멤버 변수들 역시 액세스 제한자를 통해 외부의 개체로부터 접근에 대한 권한을 설정할 수 있다.

다음 소스를 보자.

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

32

33

34

35

36

using System;

using System.Collections.Generic;

using System.Text;

 

namespace MemberVariable_Sample1

{

    class Program

    {

private MyClass myClass;

 

        static void Main(string[] args)

        {

            new Program().Test();

           

            Console.ReadLine();

        }

 

        private void Test()

        {

            myClass = new MyClass();

 

            Console.WriteLine(myClass.integer1);

            //Console.WriteLine(myClass.integer2);

        }

    }

 

    public class MyClass

    {

        public int integer1 = 10;

        private int integer2 = 10;

 

        public MyClass()

        {

        }

    }

}

 

이 소스는 Program이라는 클래스와 MyClass라는 2개의 클래스를 가지고 있다. 실행은 Main 메서드를 포함하는 클래스에서부터 시작된다는 걸 앞에서 다루었다. 프로그램 클래스의 9번재 줄은 MyClass에 대한 개체 인스턴스를 myClass라는 변수명으로 선언하고 있다. 실제 MyClass라는 인스턴스 변수는 18번째 줄의 Test 함수 안에서 사용되고 있다. 13번째 줄에서 Test라는 메서드를 호출하고 있는데, 메서드명을 바로 써서 호출하지 않고 클래스명에 멤버 참조로 호출하는 이유는 Main메서드가 static 메서드이기 때문이다.

18번째 줄의 Test메서드를 설명하기 앞서 27번째 줄에서부터 구현된 MyClass라는 클래스를 보자. 이 클래스는 생성자와 integer1, integer2라는 2개의 정수형 멤버 변수가 선언되어 있다. 이 변수들은 각각 public private의 액세스 한정자를 가지고 있다.(액세스 한정자에 대해서 상세한 내용은 뒤에서 다루고 있다.) 이와 같이 메서드 내부가 아닌 클래스에서 선언되는 변수를 멤버 변수라고 부른다. 실제 호출되는 구문을 보기 위해 이제 18번째 줄의 Test 메서드를 보면, 우선 MyClass라는 인스턴스를 이용하기 위해 MyClass의 생성자를 호출하고 있다. 그 후 22번째 줄에서 myClass라는 생성된 인스턴스 변수명으로 integer1이라는 정수형 멤버 변수를 가져오고 있다. 23번에 주석 처리된 구문은 integer2 private로 접근이 제한됨으로 실제 여기서는 사용할 수 없고, MyClass 클래스의 integer2의 액세스 제한자를 public로 고치면 사용 가능해진다.

 

4) 메서드

일반적으로 메서드는 다음과 같은 형식으로 선언된다. 메서드는 실제 수행 코드가 들어가 작업을 수행하는 역할을 한다.

/액세스 한정자/ [반환타입] [메서드 이름](매개변수)

 

앞서 만든 Car 클래스를 생각해보자. Car의 기능은 출발과 정지의 2가지 기능이 있었다. 이를 doStart doStop라는 메서드로 만들었던 것이 기억이 날것이다. 여기에서는 이 메서드를 이용해 실제 자동차의 기능을 이용하는 방법에 대해 알아보기로 하겠다.

 

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

32

33

34

35

36

37

38

39

40

41

42

43

44

45

46

47

48

49

50

51

namespace Method_Sample1

{

    class Program

    {

        Car carClass;

 

        static void Main(string[] args)

        {

            new Program().CarTest();

           

            Console.ReadLine();

        }

 

        private void CarTest()

        {

            carClass = new Car();

 

            carClass.doStart();

        }

    }

 

    public class Car

    {

        private bool IsEngineOn;

        private bool EngineBreak;

        private int SteeringWheel;

       

        public Car()

        {

        }

 

        public void doStart()

        {

            EngineBreak = false;

            IsEngineOn = true;

            Console.WriteLine(EngineStatus(IsEngineOn));

        }

 

        public void doStop()

        {

            EngineBreak = true;

            IsEngineOn = false;

            Console.WriteLine(EngineStatus(IsEngineOn));

        }

 

        private string EngineStatus(bool isRun)

        {

            return "RunAvailable : " + IsEngineOn.ToString();

        }

    }

}

 

16번째 줄을 보면 Car 클래스에 대한 인스턴스를 생성하고 doStart 메서드를 호출하고 있는 것을 볼 수 있다. 호출되는 32번째 줄에 doStart 메서드는 액세스 한정이 public으로 선언되어 있고 리턴 타입이 void로 되어 있다. 실제 doStart 메서드의 내용을 보면 멤버 변수 EngineBreak의 값과 IsEngineOn의 값을 바꾸고 EngineStatus 메서드를 호출해서 리턴값을 출력해주는 것을 볼 수 있다.

46번째 줄의 EngineStatus 메서드를 보자.

 

private string EngineStatus(bool isRun)

{

return "RunAvailable : " + IsEngineOn.ToString();

}

 

EngineStatus 메서드는 bool 타입으로 isRun이라는 이름으로 매개변수를 받는 다는 것을 알 수 있을 것이다. doStart에서 보면 36번째 줄의 EngineStatus(IsEngineOn)와 같이 IsEngineOn을 매개변수로 EngineStatus를 호출하는 것을 볼 수 있다. EngineStatus 메서드는 IsEngineOn의 값을 isRun이라는 변수에 받아 지역 변수를 생성하게 된다.

48번째 줄의 return 이라는 키워드는 함수의 반환타입이 void가 아닌 경우 반듯이 있어야 한다. return이라는 키워드를 통해 반환타입과 동일한 타입의 값을 리턴하게 되는 것이다. 여기서는 “RunAvailable : True”라는 문자열을 리턴하게 된다. 그리고 최종적으로 36번째 줄의 Console.WriteLine에 결과값이 들어가게 된다. 실제 36번째 줄의 출력 구문은 아래와 같이 함수가 리턴 받은 값이 들어가서 출력되게 된다.

Console.WriteLine( “RunAvailable : True”);

 

3.1.2.2 액세스 한정자

액세스 한정자는 클래스의 접근 권한을 컨트롤하기 위해 이용된다. 실제 사용 가능한 형식은 public, private, protected, internal, protected internal 이 있다.

 

1) public private

public private는 가장 대표적으로 이용되는 형식 멤버에 대한 엑세스 한정자이다. 2개의 차이는 public 는 액세스 제한이 한정되지 않는데 반해, private는 액세스가 포함하는 형식으로 제한된다는 데 있다. 다음 소스는 클래스에 대한 접근 제한을 보이고 있다.

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

using System;

using System.Collections.Generic;

using System.Text;

 

namespace Class_Sample1

{

    class Program

    {

        static void Main(string[] args)

        {

            new MyClass().Method();

 

            Console.ReadLine();

        }

    }

 

    public class MyClass

    {

        public void Method()

        {

            Console.WriteLine("Test");

        }

    }

}

 

17번째 라인을 보면 MyClass라는 클래스를 액세스 한정자 public로 해서 만들고 있는 것을 볼수 있다. 이와 같은 경우 MyClass public으로 액세스 한정되기 때문에 다른 클래스에서 접근이 가능하게 된다. public private로 고치게 되면 액세스 제한 문제로 인해 컴파일 오류가 발생한다.

 

2) protected

protected 액세스 한정자는 멤버 액세스 한정자로 이용된다. 이는 클래스 상속 관계 하에서 상속받는 자식 클래스만이 부모클래스의 멤버에 대한 접근이 가능하도록 만들어 준다. 다음 예제를 보자.

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

32

33

34

35

class Program : Car

{

Program pg;

 

    static void Main(string[] args)

    {

        new Program().CarTest();

        Console.ReadLine();

    }

 

    private void CarTest()

    {

        pg = new Program();

        pg.IsEngineOn = true;

    }

}

 

public class Car

{

    protected bool IsEngineOn;

    protected bool EngineBreak;

    protected int SteeringWheel;

   

    public Car()

    {

    }

 

    public void doStart()

    {

    }

 

    public void doStop()

    {

    }

}

 

1번째 줄을 보면 지금껏 보지 못했던 구문이 나온다. Program 클래스 뒤에 콜론(:)이 나오고 뒤에 Car이라고 되어 있는 것을 볼 수 있을 것이다. 이는 18번째 줄부터 구현되어 Car 클래스를 상속 받아 Program 클래스가 구현된다는 것을 알려주는 구문이다. 상속의 의미는 부모의 멤버 변수와 멤버 메서드를 물려받는 다는 것을 의미한다.(상속에 대해서는 다음에 설명하도록 하겠다.)

18번째 줄부터 구현되어 있는 Car 클래스의 멤버 변수 IsEngineOn, EngineBreak, SteeringWheel 을 보자. 이들에 대한 액세스 제한자는 모두 protected로 선언되어 있는 것을 볼 수 있다. 11번째 줄의 CarTest 메서드는 Program 클래스의 인스턴스를 생성하고 IsEngineOn의 값을 true로 변경하는 코드를 포함하고 있다. 앞서 말한 상속 관계 하에서 Program 클래스는 Car 클래스의 자식 클래스가 되고 protected 로 선언된 멤버는 자식 클래스에서 이용 가능하다고 적었었다. 그 이유로 실제 Car 클래스로부터 상속받게 되는 IsEngineOn이라는 메서드에 대한 접근이 가능하고 값을 수정할 수 있게 된다. 20번째 줄의 IsEngineOn 변수에 대한 액세스 제한자를 private로 고치고 다시 프로그램을 컴파일 해보면 보호 수준 때문에 컴파일 에러가 나는 것을 볼 수 있을 것이다.

Public 키워드와의 차이는 public 키워드는 무조건 공개되지만, protected 키워드는 상속받고 있는 자식에게만 공개된다는 것이다.

 

3) internal

액세스가 현재 어셈블리로 제한된다.

 

4) protected internal

현재 어셈블리 또는 포함하는 클래스에서 파생된 형식으로 액세스가 제한된다.

 

3.1.3 중첩 클래스

클래스는 중첩되어 사용될 수 있다. 중첩 클래스의 접근은 액세스 한정자에 따라 제한된다.

아래는 중첩 클래스의 예이다.

1

2

3

4

5

6

7

8

9

public class ParentClass

{

    public class NestedClass

    {

        public NestedClass()

        {

        }

    }

}

 

위의 소스에서 선언된 내부 클래스에 대한 인스턴스 생성은 다음과 같이 할 수 있다.

ParentClass.NestedClass nestedClass = new ParentClass.NestedClass();

 

3.1.4 partial 클래스

클래스나 구조체, 인터페이스를 분할하기 위해서 partial 이라는 키워드를 이용하는 방법을 C#에서는 제공하고 있다. 규모가 큰 프로젝트의 경우 하나의 클래스 크기가 엄청나게 커 질 수 있다. 이와 같은 경우 동일한 성격의 메서드별로 나눠 클래스를 분리하여 관리하거나 여러 사람이 동시에 작업을 진행하기 위해 partial 클래스를 이용할 수 있다.

추상 클래스는 abstract 키워드를 이용해서 만들 수 있으며 그 형식은 다음과 같다.

/액세스 한정자/  partial class [클래스 이름]

 

Windows 응용 프로그램 프로젝트를 생성해 보자. 그러면 다음과 같이 partial 클래스가 생성되는 것을 볼 수 있을 것이다.

 

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

using System;

using System.Collections.Generic;

using System.ComponentModel;

using System.Data;

using System.Drawing;

using System.Text;

using System.Windows.Forms;

 

namespace KailPaperManager

{

    public partial class Form1 : Form

    {

        public Form1()

        {

            InitializeComponent();

        }

    }

}

 

생성자를 보면 InitalizeComponent 라는 메서드를 호출하고 있는데, 현재 이 소스에서는 어디에서도 InitalizeComponent라는 메서드를 찾아 볼 수 없을 것이다. 이와 같이 여러 개의 소스에 클래스를 분할하여 사용하는 것을 가능하게 해주는 기능이 partial 클래스이다.

 

 

3.1.3 정적 클래스

정적 클래스는 .NET 2.0에서 추가된 기능으로 클래스 이름 앞에 static라는 한정자를 이용해 구성할 수 있다. 정적 클래스는 static 멤버만 가져야 하며, 생성자를 가질 수 없다.

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

namespace StaticClass_Sample1

{

    class Program

    {       

        static void Main(string[] args)

        {

            //Car carInstance = new Car();

            Car.doStart();

           

            Console.ReadLine();

        }

    }

 

    public static class Car

    {

        private static bool IsEngineOn;

        private static bool EngineBreak;

        private static int SteeringWheel;

       

        public static void doStart()

        {

            Console.WriteLine("Static Instance");

        }

 

        public static void doStop()

        {

            Console.WriteLine("Static Instance");

        }

    }

}

 

7번째 줄의 주석 처리되어 있는 부분은 Car 클래스에 대한 인스턴스를 생성하는 구문이다. 주석을 해제하면 컴파일 오류가 발생하게 된다. 이유는 정적 클래스는 정적 멤버들로만 구성되며, new 를 통해 인스턴스를 만들 수 없기 때문이다. 정적 클래스 내부에 생성자가 없는데, 정적 클래스는 생성자를 이와 같은 이유로 만들 수 없다. 14번째 줄의 정적 클래스 Car를 보면 모든 멤버들이 static 키워드를 이용해 선언되어 있는 것을 볼 수 있을 것이다.

정적 멤버는 8번째 줄과 같이 클래스 명과 참조로 바로 접근할 수 있다.

 

3.1.4 봉인 클래스(sealed class)

봉인 클래스는 sealed 키워드를 이용해 정의한다. 이와 동일하게 봉인 멤버 역시 정의가 가능하다. 봉인 클래스나 멤버는 상속 구현이 되지 않는다.


/* 출처 */
inobae의 놀이터
http://blog.naver.com/inobae

2.5 흐름 제어

일반적으로 프로그램은 작성된 코드 순서대로 실행이 된다. 이 실행 순서를 바꾸기 위해 사용되는 방법을 여기서는 설명하도록 하겠다.

 

2.5.1 조건문 if

조건문은 진리값에 따라 코드를 실행해 줄지 말지를 결정해주기 위한 문장이다.

 

1) 단순 조건문

단순 if 문의 경우 다음과 같이 이용될 수 있다.

if (상태)

    수행 코드

 

수행 코드가 여러 줄로 나타날 경우 중괄호를 이용해 묶어 줄 수도 있다.

if (상태) {

    수행 코드1

    수행 코드 2

}

 

위 문장은 단순 조건문의 기본형을 보여주고 있다. 참인 경우만을 가지고 조건을 판단할 때는 위와 같이 이용되고, 거짓인 경우에 다른 코드를 실행해주어야 한다면 다시 else 를 이용하여 조건문을 확장할 수 있다.

 

다음 문장을 보자.

if (비가 온다)  우산을 팔아라.

else 부채를 팔아라.

 

이 문장은 비가 온다라는 명제가 참일 경우 우산을 팔아라.라는 행위를 하고 거짓인 경우 부채를 팔아라.라는 행위를 하라는 의미를 나타낸다. 여기서 중요하게 보아야 할 부분은 if 문 뒤의 소괄호 안에 있는 비가 온다가 참인지 거짓인지에 대한 상태이다.

 

다음 소스를 보자.

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

Using System;

using System.Collections.Generic;

using System.Text;

using System.Runtime.Serialization;

 

namespace Second

{

    class Program

    {

        static void Main(string[] args)

        {

            bool isRaining = true;

 

            if (isRaining)

                Console.WriteLine("우산을 팔아라.");

            else

                Console.WriteLine("부채를 팔아라.");

 

            Console.Read();

        }

    }

}

 

우산을 팔아라.

 

12번째 라인에서는 비가 오는지에 대한 상태를 나타내기 위해 불리언 타입 변수 isRaining를 선언하였다. 14번째 라인은 isRaining의 값이 참인지 거짓인지 유무를 판단해서 참일 경우 15번째 라인을 거짓을 경우 17번째 라인을 출력해주는 구문이다. 결과는 12번째 라인에서 isRaining true로 초기화되어 있기 때문에 우산을 팔아라.는 문장이 출력될 것이다. 만약 isRaining false의 값이 들어가면 부채를 팔아라.는 값이 출력된다.

 

if 문은 상황에 따라 상황에 따라 실행 흐름을 제어해주는 가장 간단한 제어문의 한가지 이다.

 

2) 복합 조건문

여러 개의 값을 가지는 조건에 대해서 조건문이 어떻게 사용되는 지 알아 보리고 하겠다. 나이가 0~2 살까지는 신생아, 2살 이상 8살까지는 영아, 8살 이상 14살까지는 어린이, 14살 이상 20살까지는 청소년이라고 출력하는 프로그램을 생각해보자.

if (나이가 0이상 2보다 작으면)

 신생아 출력

if (나이가 2이상 8보다 작으면)

     영아 출력

if (나이가 8이상 14보다 작으면)

어린이 출력

if (나이가 14이상 20보다 작으면)

청소년 출력

 

위와 같이 각각의 나이의 조건에 따라 if 문을 사용하여 작성할 수 있을 것이다. 그러나 나이라는 공통 요소를 가지고 코드가 구현된다는 점을 생각하면 각각의 독립된 if 문으로 구성되는게 효과적이지 못하며, 복잡한 코드에서 오히려 코드를 읽는데 불편함으로 작용할 수 있다. 다음 소스를 보자

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

class Program

    {

        static void Main(string[] args)

        {

            int age = 0;

 

            age = Int32.Parse(Console.ReadLine());

 

            if (age >=0 && age <2)

                Console.WriteLine("신생아");

            else if (age >=2 && age <8)

                Console.WriteLine("영아");

            else if (age >=8 && age <14)

                Console.WriteLine("신생아");

            else if (age >= 14 && age < 20)

                Console.WriteLine("신생아");

            else

                Console.WriteLine("성인");

 

            Console.Read();

        }

    }

 

다음 소스는 else if 를 이용하여 하나의 상태 변수에 대한 여러 가지 상태 값을 처리한 것이다. 공통된 상태 조건인 age에 따라서 상태를 표시해 주는 것이 각각의 if 문을 이용하여 처리한 것보다 효과적임을 알 수 있다. 또한 나열된 조건을 만족하지 않는 경우를 else 문으로 따로 손쉽게 처리해주는 것을 볼 수 있을 것이다.

 

7번째 라인의 Console.ReadLine()은 값을 입력 받기 위한 구문이며, Int32.Parse()는 입력받은 문자 값을 숫자 값으로 변환해 주기 위한 구문이다. 이에 대해서는 자료형 변환을 따로 다루게 될 것이다.

 

3) 중첩 조건문

C#에서 제어문들은 중첩하여 이용할 수 있다. 여기서는 if 문이 중첩되는 예를 간단히 보여주도록 하겠다.

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

class Program

{

    static void Main(string[] args)

    {

        bool isRun = true;

        bool isPrint = true;

 

        if (isRun)

        {

            if (isPrint == true)

            {

                Console.WriteLine("true");

            }

        }

 

        Console.Read();

    }

}

 

8번째 줄의 if 문은 9번재 줄부터 14번째 줄까지 중괄호로 묶어서 isRun true이면 수행되는 구문임을 알 수 있다. 여기에 9~14번째 줄 사이에 또 다시 if 문이 중첩해서 들어가는 것을 볼 수 있다. 이와 같은 경우를 중첩 if 문이라고 부른다. 모든 제어 구문들이 중괄호 사이로 한정된다는 것을 생각하면 쉽게 이해될 것이다.

 

4) 조건 연산자 ?

조건부 연산자는 수행할 코드가 단문일 경우 간단하게 조건문을 이용할 수 있는 방법으로 제공 된다.

(조건) ? 참인 경우 : 거짓인 경우;

 

다음 코드는 isTrue라는 조건의 상태가 참일 때 화면에 true를 거짓일 때 false를 출력해준다.

Console.WriteLine((isTrue) ? "true" : "false");

 

2.5.2 반복문 for, while, do~while, foreach

반복문은 동일한 수행코드를 여러 번 수행해줘야 할 때 유용하게 이용되는 구문이다.

10개의 똑 같은 문자열을 출력해줘야 할 필요가 있을 때 출력문을 10개를 이용하여 출력할 수도 있지만, 반복문을 이용하면 코드가 더 읽기 쉬워진다. 반복의 횟수가 컴파일 타임에 결정되는 경우 출력문을 일일이 타이핑 해주는 것이 수행 속도면에서는 좀더 나을 수 있다. 그러나 일반적으로 반복의 횟수가 상태에 따라 변한다던지, 입력에 따라 변하는 경우 코드를 만드는게 불가능 해진다.

1) for

for(초기값 ; 상태 ; 증가치)

    수행할 코드

 

2) while

While(조건)

    수행할 코드

 

3) do~while

do {

    수행할 코드

} while(조건)

 

4) foreach

배열이나 컬렉션 개체에 대한 반복을 수행하기 위한 구문이다. 컬렉션은 반복기가 구현된 개체로서 그에 대한 설명은 컬렉션을 만들어 보는 장에서 다시 설명하도록 하겠다. 우선 아래 소스를 보자.

1

2

3

4

5

int[] myArray= new int[] { 0, 1, 2, 3, 4, 5};

foreach (int i in myArray)

{

Console.WriteLine(i);

}

 

정수형 배열을 만들고 그 값을 반복하는 구문을 나타내고 있다.

2번째 줄의 foreach문은 정수배열 myArray의 요소들을 i에 담으며 반복을 수행한다.

 

2.5.3 switch

switch 문장의 기본형은 다음과 같다.

switch(상태 변수)

{

    case 상태변수 값 1 :

        수행할 코드

        break;

case 상태변수 값 2 :

        수행할 코드

        break;

case 상태변수 값 3 :

        수행할 코드

        break;

    default :

        수행할 코드

}

break의 의미는 switch 구문을 종료한다는 의미이다.

다음 소스를 보자

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

namespace switch_Sample

{

    class Program

    {

        static void Main(string[] args)

        {

            int index = 0;

 

            index = Int32.Parse(Console.ReadLine());

 

            switch (index)

            {

                case 0 :

                    Console.WriteLine("0이입력되었습니다.");

break;

                case 1:

                    Console.WriteLine("1이입력되었습니다.");

                    break;

                case 3 :

                case 4 :

                case 5 :

                    Console.WriteLine("3, 4, 5중하나의숫자가입력되었습니다.");

break;

                default :

                    Console.WriteLine("6이상의숫자가입력되었습니다.");

 break;

            }

Console.ReadLine();

        }

    }

}

 

 

 

2.5.4 break, continue, goto

 

1) break

switch에서 이미 한번 보여진 바 있다. 제어 구문에 쓰여서 제어구문을 빠져 나오라는 의미로 해석하면 된다. break는 반복문 등에 이용되어진다. 다음 소스를 보자.

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

namespace break_Sample

{

    class Program

    {

        static void Main(string[] args)

        {

            bool isExit = false;

 

            for ( ; ; )

            {

                isExit = bool.Parse(Console.ReadLine());

                if (isExit)

                    break;

                else

                    Console.WriteLine("계속");

            }

 

            return;

        }

    }

}

 

13번째 줄은 입력된 isExit 변수가 참일 경우 break문을 이용하고 있다. break 문은 반복되는 for문을 종료하는 역할을 수행한다.

 

2) continue

주로 반복문에서 조건문과 함께 이용되며, 조건부 이하 실행 후 반복이나 실행하지 않고 반복을 제어하는데 이용된다. 다음 소스를 보자. 이 소스에서는 break문과 continue문이 같이 이용되고 있다.

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

namespace Continue_Sample

{

    class Program

    {

        static void Main(string[] args)

        {

            int index = 0;

 

            for ( ; ; )

            {

                index = Int32.Parse(Console.ReadLine());

 

                if (index == 0)

                {

                    Console.WriteLine(index);

                    continue;

                }

                else if (index == 1)

                    break;

 

                Console.WriteLine("반복");

            }

            return;

        }

    }

}

 

 

3) goto

코드의 수행 순서를 바꾸기 위해 goto 키워드를 이용할 수 있다. goto 키워드에 레이블을 달아서 사용하면 지정된 레이블로 제어의 흐름이 이동하게 된다. 흔히 사용하지 않는 방법이지만, 프로그램을 깔끔하게 만들어 줄 수 있다. break문이 이용된 소스를 goto문을 이용해 바꿔보도록 하겠다. 다음 소스를 보자.

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

Namespace Goto_Sample

{

    class Program

    {

        static void Main(string[] args)

        {

            bool isExit = false;

 

            for ( ; ; )

            {

                isExit = bool.Parse(Console.ReadLine());

                if (isExit)

                    goto exit;

                else

                    Console.WriteLine("계속");

            }

 

        exit:

            return;

        }

    }

}

필요치 않은 goto 문의 빈번한 이용은 소스코드를 읽기 어렵게 만들 수 있다.


/* 출처 */
inobae의 놀이터
http://blog.naver.com/inobae

2.4 변수와 상수

2.4.1 변수

 

1) 변수의 선언

변수는 변수의 데이터 타입과 변수의 이름과 값으로 이루어진다. 변수의 선언이라 함은 미리 어떤 형의 변수를 이용하겠다고 컴파일러에게 알려주기 위해 이용되는 일련의 행위라고 보면 된다. 그럼, 왜 변수를 사용하는가?

고급 언어들 중 변수의 선언을 굳이 필요로 하지 않는 언어도 있지만, 이는 언어에 따른 차이일 뿐, 대부분의 프로그래밍 언어는 변수의 선언를 사용자에게 요구한다. 변수를 선언하지 않을 경우, 컴파일러는 다양한 형들에 대해 값이 쓰인 용도를 파악해가며 변수의 형을 내부적으로 재정의 해야한다. 이는 프로그램의 성능에 좋지 않은 영향을 미치게 된다. 다른 이유로 정의되지 않은 형은 결과에 어떤 영향을 줄지 모르며 실행시간에 런타임 에러를 일으킬 소지를 가지게 된다.

 

2) 변수명

변수명은 C에서와 마찬가지로 만들어 주면 된다.

 

 

2.4.2 상수

아래의 소스에서 Hello, C#.이라는 문자열 값은 컴파일 시에 바이너리에 만들어지며, 컴파일 후 실행 중 이 값을 바꾸는 것이 불가능 하다. 이와 같이 컴파일시에 값이 결정되는 값을 상수라고 부르며, 정해진 값은 변경이 불가능하다.

Console.WriteLine("Hello, C#.");

 

상수를 선언하기 위해서 C#에서는 const라는 키워드를 이용할 수 있다. 상수명은 일반 변수명 명명 규칙을 따라 선언하면 되며, 한번 선언된 상수의 값은 변경이 불가능하다. 코드상에서 상수값에 대한 변경을 시도하면 컴파일 오류가 발생하게 된다. 또한, 선언된 상수는 내부적으로 클래스의 static 멤버 형태로 정의되며, 따라서 외부 클래스에서 공개된 상수에 대한 접근을 위해서는 static 멤버에 대해 접근하는 것과 동일한 방법으로 접근해야 한다. 상수는 static의 특성을 가지기 때문에 static 키워드와 함께 사용할 수 없다.

상수를 이용하는 이유는 미리 정의된 값을 알아보기 쉬운 형태의 이름을 줘서 명시적인 의미를 알아보기 쉽도록해서 사용하기 편하도록 하기 위해서라 보면 된다. 다음 소스를 보자.

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

using System;

using System.Collections.Generic;

using System.Text;

 

namespace Constant_Sample

{

    class Program

    {

        static void Main(string[] args)

        {

            Console.WriteLine(Math_Const.PI);   // 상수 PI 출력

            Console.Read();           

        }

 

    }

 

    public class Math_Const

    {

        public const double PI = 3.141592;  // 상수 PI 선언

    }

}

 

11번째 라인은 선언된 상수값을 출력하는 것을 보여주고 있다. static 멤버가 아님에도 불구하고 출력시 바로 클래스 이름뒤에 .을 찍고 변수명을 사용하는 것을 볼 수 있다.

19번째 라인은 PI라는 상수를 선언하고 있다. 이값은 컴파일 시에 초기화 되며, 이후 변경이 불가능하다.

 

 

* 런타임시 초기화 가능하고 상수와 같이 처음 선언된 값이 변경이 불가능한 멤버 변수를 만들기 위해서는 readonly 키워드를 이용할 수 있다.

 

 

 

2.4.3 변수의 범위

 

1) 지역변수(Local Variable)와 전역변수(Global Variable)

지역 변수는 메서드 내부에 선언되는 변수를 말한다. 지역 변수는 메서드가 호출될 때 초기화되고 사용이 끝난 후 반환된다.

전역 변수는 클래스에 멤버 필드로 선언되는 변수를 말한다. 전역 변수는 선언된 클래스 내의 모든 메서드들에서 접근이 가능하다. 클래스의 인스턴스가 생성될 때 초기화되며 클래스에 대한 자원이 반환될 때 같이 반환된다.

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

32

33

34

using System;

using System.Collections.Generic;

using System.Text;

 

namespace Variable_Test

{

    class Program

    {

        string var1 = "전역 변수.";

 

        static void Main(string[] args)

        {

            new Program().MyMethod1();

            new Program().MyMethod2();

 

            Console.Read();

        }

 

        private void MyMethod1()

        {

            string var2 = "MyMethod1의 지역 변수.";

            Console.WriteLine(var1);

            Console.WriteLine(var2);

        }

 

        private void MyMethod2()

        {

            string var2 = "MyMethod2의 지역 변수.";

            Console.WriteLine(var1);

            Console.WriteLine(var2);

            Console.WriteLine(var3);

        }

    }

}

 

전역 변수.

MyMethod1의 지역 변수.

전역 변수.

MyMethod2의 지역 변수.

 

9번째 라인은 전역 변수 var1을 선언하고 있고 21, 28번째 라인은 지역변수 var2를 선언하고 있다. 9번째 라인의 전역변수 var1을 보면 Program 클래스의 멤버 필드로 선언된 것을 볼 수 있다. 이에 반해, 21번째 라인의 var2 MyMethod1 메서드의 지역변수로, 28번째 라인의 var2 MyMethod2의 지역변수로 선언되어 있다. MyMethod1 MyMethod2는 모두 var1 var2를 출력하고 있는데, var1은 전역 변수로 선언되어 있기 때문에 결과에서와 같이 동일한 값이 출력되고, var2는 지역 변수이므로 각각 다른 값이 출력된다. MyMethod2 28번째 라인인 var2 선언부를 지우게 되면 지역 변수 var2가 선언없이 30번째 라인에 사용되었다고 컴파일 오류가 발생한다. MyMethod1 MyMethod2에 동일한 이름의 var2를 선언하고 있는 것을 볼 수 있는데 이는 메서드 내부의 지역변수이기 때문에 이름이 같아도 상관없다.

일반적으로 변수의 범위는 중괄호 {} 안에서 선언된 변수는 그 범위에서만 사용가능하다고 보면 쉬울 것이다.

 

2) 정적 변수(Static Variable)

정적 변수는 static 키워드와 함께 선언된 변수를 말한다. 정적 변수는 개체의 인스턴스를 단 한번만 생성하며, 프로그램이 종료될때까지 리소스를 반환하지 않는다. 따라서, 공개범위에 따라 정적 변수는 사용상에 좀더 자유로움이 주어진다. 그러나 메모리 상에 프로그램의 생명주기와 함께 상주하기 때문에 과다한 사용은 많은 메모리를 점유하게 되는 원인이 될 수 있다. 메서드 내부의 지역 변수에 대해서는 static 키워드를 이용한 선언이 불가능한데, 앞서 설명한 정적 변수의 특성때문이라고 보면 된다. 정정 변수와 정적 메서드는 프로그램의 생명주기와 함께 한다는 것만 기억하면 사용하는데 어려움이 없을 것이다.


/* 출처 */
inobae의 놀이터
http://blog.naver.com/inobae

2.3 연산자와 표현식

2.3.1 산술 연산자

산술 연산자는 값을 계산하기 위한 연산자이다.

연산자

용도

    

+

더하기

3+2=5

 

-

빼기

3-2=1

 

*

곱하기

3*2=6

 

/

나누기

3/2=1.5

 

%

나머지 연산

3%2=1

나머지 값을 구함

++,--

증가,감소

1++

후위 표기, 전위 표기 다름

 

 

2.3.2 논리 연산자

논리 연산자는 논리 값(true, false)를 얻기 위한 연산자이다.

연산자

용도

     

&

and 연산

a&b

 

|

or 연산

a|b

 

^

exclusive or 연산

true^true=false

동일 논리 값일 때 false

!

not 연산

!true=false

논리 값에 대한 not

&&

조건식에서

if(a==true&&b==true)

 

||

조건식에서

if(a==true||b==true)

 

 

 

2.3.3 기타 연산자

여기서는 산술 연산자와 논리 연산자에서 다루지 않은 연산자를 다룬다.

연산자

용도

     

+

문자열 결합

a"+"b"

문자열을 결합하는데 이용

<<,>>

shift 연산

 

비트 단위로 shift(곱셈, 나눗셈)

=,+=,-=,^=,*=,/=

대입 연산

 

변수에 값을 대입

<,>,==,!=,>=.<=, as, is

비교 연산

a<b

값이나 형식에 대한 비교

.

멤버 연산

 

객체의 멤버에 대한 접근

()

cast 연산

int i=(int)a;

형 변환

*, &, ->

포인트 연산

 

unsafe모드에서 포인트 이용

typeof

타입 연산

 

객체의 형에 대한 조회

??

 

 

nullable 형식 결합

checked, unchecked

예외 연산

 

오버플로에 대한 명시적 검사

?

조건부 연산

 

if 문과 유사한 역할 수행



/* 출처 */
inobae의 놀이터
http://blog.naver.com/inobae

2.2 데이터 형식과 변수

C#은 데이터 형식에 대해 값 형식과 참조 형식을 지원한다. 값 형식의 데이터 형식으로 선언된 변수는 스택(Stack)에 있는 데이터를 변수가 직접 가르키는 반면, 참조 형식의 데이터 타입으로 선언된 변수는 스택(Stack)에는 데이터에 대한 참조만을 가지며 실제 데이터는 힙(Heap)에 저장된다. 여기서 데이터 형식(Data Type)은 데이터를 나타내는 형식을 표현한 키워드라고 보면 된다.

 

C# 키워드

.NET 형식

크기

초기값

범위/정밀도

형식

bool

System.Boolean

1

false

 

byte

System.Byte

1(+)

0

0~255

sbyte

System.SByte

1

0

-128~127

char

System.Char

2

'\0'

 0x0000 ~ 0xffff

decimal

System.Decimal

16

0.0M

1.0×10-28 ~ 7.9×1028,

28자릿수 정밀도

double

System.Double

8

0.0D

5.0×10-324 ~ 1.7×10308,

15자릿수정밀도

float

System.Single

4

0.0F

1.5×10-45 ~ 3.4×1038,

7자릿수 정밀도

int

System.Int32

4

0

-2,147,483,648

~2,147,483,647

uint

System.UInt32

4(+)

0

0~4,294,967,295

long

System.Int64

8

0L

-9,233,372,036,854,775,808

~9,233,372,036,854,775,807

ulong

System.UInt64

8(+)

0L

0~

18,446,744,073,709,551,614

short

System.Int16

2

0

-32,768~32,767

ushort

System.UInt16

2(+)

0

0~65,535

string

System.String

 

null

 

참조

object

System.Object

 

null

 

참조

struct

 

 

 

 

enum

 

 

 

 

interface

 

 

 

 

참조

delegate

 

 

 

 

참조

*(point)

 

 

 

 

참조

?(nullable)

System.Nullable

 

 

 

 

 

변수는 어떤 형식을 가진 값을 담기 위한 공간을 가르키는 이름이다.

 

2.2.1 정수(int, short, long, uint, ushort, ulong)

정수 형에는 int, short, long 3가지 타입과 이 3가지 타입에서 부호를 사용하지 않는 uint, ushort, ulong 타입을 포함해 총 6개의 데이터 형식이 존재한다.

 

* 부호의 표현

정수는 양의 정수, 0, 음의 정수로 나누어 진다. 보통 수학에서 음의 정수는 -기호를 이용해서 표현하는데, 이를 컴퓨터에서 표현하기 위해서는 마찬가지로 정해진 데이터 형식에 부호를 나타내는 공간이 필요하게 된다. 이를 위해 보통 최상위 비트를 음수 표시 영역으로 정의해 두고 미리 사용하곤 한다. C#정수형 int 의 경우 4바이트 즉 32 비트를 이용해 숫자를 표현하는데 이 중 최상위 1 비트가 음수 부호를 표시하기 위해 예약되어 있어 사용하지 못하므로 값의 표현 범위는 2 31승으로 표현된다. 만약, 어떤 변수가 음의 수를 갖지 않는다는 것을 명확히 알 수 있다면 음수 부호를 표시하기 위한 영역이 필요없을 것이다. 정수형에서 이 경우 부호 없는 정수 uint와 같이 사용할 수 있으며, 이 경우 부호 비트가 따로 필요치 않으므로 값의 표현 범위는 부호 비트 1을 더해 2 32승으로 표현된다.

 

1) int, uint

 

2) short, ushort

 

3) long, ulong

 

 

2.2.2 실수(float, double, decimal)

실수 형에는 float, double, decimal 3가지 형식이 존재한다. 컴퓨터에서 실수 형을 다루는 경우 컴퓨터의 실수 표현 방법으로 인해 많은 주의를 기울여야 한다.

 

1) float(System.Single)

float 타입은 32비트 부동 소수점 값, 일반적으로 실수라고 부르는 타입을 표현하기 위해 많이 사용되는 타입이다. 값의 범위는 1.5×10-45 ~ 3.4×1038의 근사범위를 가지며 전체 7자리의 숫자까지 표현이 가능하다.

float 형식으로 숫자 리터럴이 처리되도록 하기 위해서는 접미사 f 또는 F를 이용한다.

 

2) double(System.Double)

Double 64비트의 부동 소수점 값, float보다 2배의 공간을 차지하는 실수 값을 표현하기 위한 타입이다. 값의 범위는 5.0×10-324 ~ 1.7×10308의 근사범위를 가지며 전체 자릿수는 15~16개의 자리 숫자까지 표현이 가능하다.

C#에서는 실수형 상수 표기는 기본적으로 double형으로 인식된다. 다음에 사용된 1.5 double형으로 사용된다.

int i = i+1.5;

 

3) decimal

decimal은 부동 소수점 값을 표기하기 위한 형식으로 16바이트의 크기를 가진다. 28~29개의 유효 자릿수에 대한 표기가 가능하다. .NET 형식은 System.Decimal에 해당한다.

decimal 형식으로 숫자 리터럴이 처리되도록 하기 위해서는 접미사 m 또는 M을 이용한다.

 

2.2.3 byte

byte 형은 8비트 정수를 값을 저장하기 위한 데이터 형식으로 .NET 형식의 System.Byte에 해당한다. 값의 범위는 0~255까지의 정수형을 저장할 수 있으며, 소켓 프로그램이나 시리얼 통신과 같이 바이트 단위의 값 교환이 필요한 경우에 이용된다.

 

부호 있는 형태의 바이트 형식을 위해 sbyte라는 형식이 따로 존재한다.

 

2.2.4 불리안(Boolean)

불리안(boolean) 형은 bool 키워드를 이용해 사용하여 표기한다. .NET 형식의 System.Boolean 형에 해당되며 거짓의 진리 값을 표현하기 위해 이용된다.

아래 소스는 불리안(Boolean) 변수를 하나 선언하고 진리 값을 출력해주는 소스이다.

 

1

2

3

4

5

6

7

8

9

10

11

12

13

 

14

15

16

17

using System;

using System.Collections.Generic;

using System.Text;

 

namespace ConsoleApplication1

{

    class Program

    {

        static void Main(string[] args)

        {

bool isTrue = false;

System.Boolean isFalse = true;

Console.WriteLine("Is True : {0}, Is False :{1}",

isTrue.ToString(), isFalse.ToString());

              Console.Read();       

}

    }

}

 

11, 12번째 라인은 불리안 형 변수 isTrue isFalse 를 선언하고 값을 각각 true false로 초기화 하고 있다. 11번째 줄의 bool  키워드와 System.Boolean은 동일한 의미로써 불리안 형식을 선언하는데 이용되는 2가지 형식 선언을 보여준다.

13번째 줄은 isTrue isFalse의 변수 값을 출력해주는 문장이다.

 

 

2.2.6 문자(char)

문자를 저장하기 위한 형식으로 char 키워드를 제공하는데, .NET 형식은 System.Char에 해당하며, 유니코드 문자로 2바이트의 크기를 가진다. Char는 구조체로 구현되어 있으며, 변환과 비교를 위한 몇 가지 메서드를 가진다.

문자형을 값은 홑 따옴표를 이용하여 다음과 같이 표기된다.

char ch1 = 'A';

char ch2 = '\x0041';

 

ch1 ch2는 동일한 값을 A를 가진다. char 형은 보여지는 이와 같이 문자 리터럴과 유니코드로 표현할 수 있다.

아래의 코드를 보자.

char ch = 65;

Console.WriteLine("{0}",ch );

C C++를 해보았다면 결과를 A가 출력된다고 잘못된 판단을 내릴 수 있다. C# char은 숫자 리터럴로 쉽게 변환할 수 있지만, char 형은 정수 표기 데이터 형식과는 다른 구조를 가진다. 따라서 이 코드는 컴파일 시 상수 값을 char 형으로 변환할 수 없습니다.라는 컴파일 에러가 뜨게 된다.

 

2.2.6 문자열(string)

C#에는 문자열을 저장할 수 있는 데이터 형식으로 string 키워드를 제공한다. .NET 형식은 System.String 에 해당하며, 유니코드 문자열 형태로 값이 들어가게 된다.

문자열은 쌍 따옴표로 묶어서 다음과 같이 표기한다.

string str = "String";

 

실제 문자열을 저장하기 위한 String 클래스는 내부적으로 char 포인터 형태로 구현되어 있다. 이러한 이유로 string 개체는 char의 배열 형태로 각각의 char에 접근하는 것이 가능하다. 여기서는 간단한 string 형식에 대한 설명이 목적이므로, String 클래스에 대해서는 나중에 따로 다루게 될 것이다.

* 문자열 개체는 생성된 이후 개체의 내용에 대한 변경이 불가능하다. 이는 메모리 재할당보다는 개체 생성과 반환을 통한 문자열 관리가 더 편리해서 이와 같은 방식으로 구현된 듯 하다.

 

이스케이프 문자는 특수한 기능을 가진 문자들로서 화면에 출력하거나 프린트로 출력할 때 여러 기능들을 표현 할 수 있다.

문자 출력 시 쌍 따옴표나 홑 따옴표, 백 슬래쉬 등의 출력과 같은 경우 역시 이스케이프 문자를 이용하여 손쉽게 출력할 수 있다.

Console.WriteLine("\tstring \rprint \"test\"");

 

아래 표는 C#에서 사용되는 이스케이프 문자를 정리한 표이다.

2. 이스케이프 문자

이스케이프 문자

결과

유니코드

\0

Null

0x0000

\a

경고음

0x0007

\b

백스페이스(Backspace)

0x0008

\t

수평 탭

0x0009

\n

새 줄(new line)

0x000A

\v

수평 탭(프린트 시)

0x000B

\f

폼 피트(프린트 시)

0x000C

\r

캐리지 리턴(Carriage return)

0x000D

\

쌍 따옴표

0x0022

\

홀 따옴표

0x0027

\\

백 슬래쉬(Backslash)

0x005C

\uABCD

\xABCD

유니코드 문자

0xABCD

 

2.2.6 nullable [inobae1] 

nullable 형식은 일반적인 데이터 형식에 추가로 null 값을 나타내기 위해 C# 2.0에서 추가된 개념이다. nullable 형식은 다음과 같이 2가지 방법으로 표현될 수 있다.

System.Nullable<T> nType;

T? nType;

 

여기서 T는 구조체를 비롯한 값 형식이 될 수 있지만 참조 형식은 될 수 없다.

다음은 int 형 변수 i nullable 형으로 선언하기 위한 2가지 방법이다.

System.Nullable<int> i;

int? i;

 

nullable 형의 데이터 형식을 이용하는 이유 중 하나는 데이터베이스의 bool 형식을 들 수 있다. Sql-Server의 불리안 형식은 일반적인 true, false 값 외에 null의 값을 포함해서 3가지 상태를 가진다. 그러나 기본 bool 형식은 2개의 값 true, false 밖에 가지지 못한다. 따라서 nullable 불리안 형식을 사용하면 이에 대한 자연스러운 처리가 가능하게 된다.

bool flag1 = null;   // 에러

bool? flag2 = null;  // 정상

 

다음은 nullable 형식을 이용해 선언한 데이터 형식들에 대한 예를 몇 가지 보여주고 있다.

int? i = 10;

double? d1 = 3.14;

bool? flag = null;

char? letter = 'a';

int?[] arr = new int?[10];

 

nullable 형식은 추가로 다음과 같은 2개의 읽기 전용 속성을 가진다.

HasValue : bool 형식이며, 변수에 null이 아닌 값이 포함되어 있으면 true, 아니면 false를 가진다.

Value : null이 아닌 경우 값을 나타내며, null 인 경우 InvalidOperationException을 발생 시킨다.

int? y = 10;

if(y.HasValue) Console.WriteLine(y.Value);

else Console.WriteLine("정의되지 않음");

 

1) 명시적 변환

nullable 형식은 Value 속성을 사용하거나 명시적으로 캐스팅하여 일반 형식으로 캐스팅 하는 것이 가능하다. 그러나 실제 null 값을 가질 경우 경우 InvalidOperationException 예외가 발생하게 된다. 명확한 처리를 위해서는 HasValue 속성을 통해 값의 존재 유무를 확인하는 방법이 가장 적절한 방법이 된다.

int? n = null; 

int m1 = n;         // 일반변수에 null이 할당되어 컴파일 에러가 발생함.

int m2 = (int)n;    // 컴파일은 되나 널값에 대해 exception을 발생시킨다.

int m3 = n.Value;   // 컴파일은 되나 널값에 대해 exception을 발생시킨다.

 

2) 암시적 변환

일반 형식에서 nullable 형식으로 변환이 이루어질 경우 암시적 변환이 이루어 진다..

int? n2 = 10;  // 암시적 변환

 

3) 산술 연산자

값 형식에서 사용되는 연산자들은 unllable에도 모두 사용할 수 있다. 산술 연산자에서 피 연산자가 null 인 경우는 연산의 결과는 무조건 null 값이 된다. 그 외의 경우는 일반 형식의 연산 결과와 동일하다.

int? a = 10;

int? b = null;

a++;             // 1이 증가하여 11

a = a * 10;      // 10을 곱하여 110

a = a + b;       // 110null을 더하면 null

 

비교 연산의 경우 nullable 형식이 하나라도 null이면 비교 결과는 항상 false가 된다. 따라서, 비교의 결과가 false라고 해서 그 반대의 경우가 true가 된다고 단정할 수 없다.

int? num1 = 10;

int? num2 = null;

if(num1>= num2) System.Console.WriteLine("num1>=num2");

else System.Console.WriteLine("num1<num2");

// num2 null이므로 비교결과는 false, num1 < num2 라고 단정할 수 없다.

 

 

4) ?? 연산자

?? 연산자는 null이 허용되지 않는 형식에 nullable 형식을 대입할 때 반환되는 기본 값을 정의 하며, 중첩 사용이 가능하다.

int? c= null;

int d = c ?? -1;    // d=c이고, c null 이면 d = -1 이 된다.

int? e = null;

int? f = null;

int g = e ?? f ?? -1;  // e, f가 둘다 null 일 경우 -1 이 되고 e null일 경우는 f

 

5) bool? 형식

bool? Nullable 형식에서는 true, false, null의 세 가지 값이 포함될 수 있다. 따라서, if, for, while 같은 조건문에서는 이를 사용할 수 없고, 사용된 경우 컴파일 오류가 발생한다. 이는 null이 조건문에서 명확한 의미를 주지 못하기 때문에 컴파일 오류가 발생한다. 조건문에서 사용되기 위해서는 앞에서 다룬 nullable 형식의 명시적 변환을 이용하면 컴파일 오류는 피할 수 있지만, 값이 null 경우 InvalidOperationException 예외가 발생하게 된다. 따라서, bool로 캐스팅하기 전에 HasValue 속성을 통해 값이 존재하는지 null인지 유무를 확인하는 것이 중요하다.

bool? b = null;

if (b) { }        // 컴파일 오류 발생

bool? b = null;

if ((bool)b) { } // 컴파일은 되나, InvalidOperationException 예외 발생

 

아래 표는 nullable 불리안 타입의 논리 연산 진리표이다.

3. nullable Boolean 연산 진리표

x

y

x&y

x|y

true

true

true

true

true

false

false

true

true

null

null

true

false

true

False

true

false

false

False

false

false

null

False

null

null

true

Null

true

null

false

False

null

null

null

Null

null


 



/* 출처 */
inobae의 놀이터
http://blog.naver.com/inobae

2. 기본 구문

2.1 namespace using

2.1.1 namespace

Java를 해보았다면 패키지(package)라는 개념에 익숙해져 있을 것이다. C#의 네임스페이스(namespace)는 이와 유사하다고 보면 쉽게 이해할 수 있을 것이다. 네임스페이스(namespace)를 이용하는 주된 목적은 네임스페이스를 이용하여 클래스를 묶어 손쉽게 관리하기 위한 목적과 네임스페이스 범위로 코드를 구성하여 사용자가 클래스 및 메서드 이름의 범위를 쉽게 제어하는데 있다.

실제 C#에서 많은 클래스들은 그들의 성격에 따라 네임스페이스(namespace)로 묶어서 관리된다. C#.NET 의 시스템 네임스페이스는 System으로 시작하며 수많은 클래스들을 포함하고 있다. 대표적인 예로 앞서 예제에서 이용된 Console클래스를 비롯하여 Int, Boolean, String과 같은 형식 선언들이 모두 System 네임스페이스로 묶여 있다.

네임스페이스는 .으로 구분되어 관리 될 수 있다.

네임스페이스의 선언은 namespace 키워드를 이용하여 선언한다.

namespace MyNamespace1.MyNamespace2

{

    class Program

    {

static void Main(string[] args)

        {

        }

    }

}

 

이 예제는 .을 기준으로 네임스페이스를 2단계로 구성하였다. 이는 아래 소스와 동일하다.

namespace MyNamespace1

{

namespace MyNamespace2

{

        class Program

        {

static void Main(string[] args)

            {

            }

        }

    }

}

 

네임스페이스는 DLL과 같은 라이브러리를 제작하여 이용할 때, 사용자 라이브러리의 기능을 명시적으로 표현해줄 수 있는 이름으로 이용되곤 한다.

* 네임스페이스를 지정하지 않고 소스코드를 작성하면 기본 네임스페이스가 자동으로 만들어진다.

* :: 연산자는 네임스페이스 별칭 한정자 연산자로 식별자 조회를 위해 이용된다.

 

2.1.2 using

C#으로 프로그램을 작성할 때 매번 빠지지 않고 나오는 키워드가 using이라는 키워드이다. using의 용도는 다음과 같다.

1) 개체 이용의 편의성

C#으로 구현된 코드들은 최상위에 using 키워드를 통해 네임스페이스를 지정하고 있는 것을 볼 수 있을 것이다. 이와 같이 이용하는 이유는 개발할 때 한정된 네임스페이스를 이용하면 네임스페이스에 대한 전체 이름을 참조하지 않고도, 선언된 네임스페이스 내부에 있는 개체들에 접근하는 것이 가능하기 때문이다. 이는 불필요한 코드 타이핑을 줄일 수 있다. 또한, 지시자를 통해 선언하고 시작함으로써 코드가 명시적으로 어떠한 네임스페이스들을 참조하여 만들어져 있는지 확인하기 쉬워진다. 실제 지시자 using을 사용하는데 있어, 주의하여야 할 것은 using C C++ include 구문처럼 헤더파일이나 라이브러리 파일을 가르는 것이 아니라 네임스페이스(namespace)이름을 나타낸다는 것이다.

앞서 사용된 예제를 다시 보면,

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

using System;

using System.Collections.Generic;

using System.Text;

 

namespace ConsoleApplication1

{

    class Program

    {

        static void Main(string[] args)

        {

            Console.WriteLine("Hello, C#.");

Console.Read();

        }

    }

}

 

1번째 라인에 using System; 구문을 통해 선언해 놓았기 때문에 11번째 라인은 System.Console.WriteLine("Hello, C#."); 대신에 Console.WriteLine("Hello, C#."); 처럼 사용할 수 있게 된다. 12번째 라인 역시 System 네임스페이스에 속한 Console클래스의 Read 메서드를 Console.Read();와 같이 사용할 수 있게 된다.

 

이외에 using은 네임스페이스의 별칭을 지정하는데 이용될 수도 있다. 다시 이전의 소스를 수정해 보자. 앞의 소스와 1번째 라인과 11, 12번째 라인을 비교해보자.

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

using MyClass = System.Console;

using System.Collections.Generic;

using System.Text;

 

namespace ConsoleApplication1

{

    class Program

    {

        static void Main(string[] args)

        {

MyClass.WriteLine("Hello, C#");

            MyClass.Read();

        }

    }

}

 

의 소스와 비교해보면 1번째 라인은 using System;  using MyClass = System.Console; 로 바뀌었다는 것을 알 수 있다. 현 소스의 1번째 라인의 의미는 System 네임스페이스의 Console 클래스에 대한 별칭을 MyClass로 주겠다는 것을 의미한다. 이렇게 이용함으로써 긴 이름이나 복잡한 이름에 대한 별칭을 쉽게 지정할 수 있고 11, 12번째 라인에서 이용한 것처럼 별칭을 이용해 메서드를 호출할 수 있게 된다.

별칭에 대한 대상은 네임스페이스나 형식 클래스가 될 수 있으며, 적절한 별칭의 사용은 코드를 읽기 쉽게 만드는데 기여할 수 있다.

 

2) 범위 한정

using의 중요한 기능 중 하나는 범위 제한에 있다. .NET Garbage Collector에 의해 제어되는데, Garbage Collector가 수행되는 시기가 객체의 사용이 끝나는 시점과 맞물리지 않는다. 이와 같은 문제는 제한된 리소스를 이용하는 경우 문제점가 발생할 수 있다. 데이터베이스 서버에 연결되어 작업되는 시스템이 있을 때, 데이터베이스 서버는 클라이언트 연결 수에 대한 제한을 가진다. 이 경우 빠른 연결해제가 이루어지지 않으면, 다른 클라이언트에서 연결 초과로 인해 데이터베이스 서버 연결에 실패하는 경우가 발생할 수 있다. 이와 같은 경우 객체의 사용과 해제를 명시적으로 하기 위해 Garbage Collector을 강제로 수행하거나 객체의 범위를 한정시켜 해당 리소스를 해제할 시기를 지정하는 방법을 이용하여야 한다. using은 이 경우 명시적인 리소스 해제 시기를 한정해줄 수 있게 된다. 아래 예제를 보자.

 

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

32

33

using System;

using System.Collections.Generic;

using System.Text;

 

namespace Second

{

    class Program

    {

        static void Main(string[] args)

        {

            using (SomeClass _some1 = new SomeClass())

            {

            }

            Console.WriteLine("Some1 Dispose");

           

            SomeClass _some2 = new SomeClass();

            using (_some2)

            {

            }

            Console.WriteLine("Some2 Dispose");

           

            Console.Read();

        }

    }

 

    class SomeClass : System.IDisposable

    {

        public void Dispose()

        {

            Console.WriteLine("test");

        }

    }

}

 

11~13번째 라인은 using을 통해 SomeClass 개체를 이용하고 있다. 이 경우 using으로 묶여있는 SomeClass는 중괄호로 묶여있는 범위 안에서만 이용되며, 이 범위를 벗어나는 순간 Dispose 메서드가 자동으로 호출되게 된다. 16~19번째 라인은 동일한 역할을 하는 코드를 다른 방법으로 표현한 예이다.

 

* 이 경우 using문에 사용되는 개체는 Dispose 메서드 호출을 통해 개체의 리소스를 해제할 수있도록 하기 위해, IDisposable 인터페이스가 구현되어야 한다.



/* 출처 */
inobae의 놀이터
http://blog.naver.com/inobae

1. 시작하기

간단한 응용 프로그램을 만들어 보자. [파일] 메뉴에서 [새로 만들기] 메뉴의 [프로젝트] 메뉴를 선택하면 다음과 같은 화면이 나타난다. [콘솔 응용 프로그램]을 선택 한 후 확인 버튼을 눌러보자.

 

확인 버튼을 누르면 다음과 같이 기본 코드가 생성되며, 11, 12번째 라인과 같이 타이핑을 한 후 실행을 해보자.

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

using System;

using System.Collections.Generic;

using System.Text;

 

namespace ConsoleApplication1

{

    class Program

    {

        static void Main(string[] args)

        {

            System.Console.WriteLine("Hello, C#.");

System.Console.Read();

        }

    }

}

 

아래와 같이 출력되는 것을 볼 수 있을 것이다.

Hello, C#.

 

자 이제 소스에 대해 살펴 보기로 하자.

1~3번째 라인을 보면 using로 시작하는 구문을 볼 수 있을 것이다. C#에서 using 키워드 크게 2가지의 역할로 요약할 수 있다. 첫 번째는 별칭의 형태로 이용하기 위한 단순 지시자의 용도가 그 하나이고 두 번째는 범위 한정을 위해 이용된다. 여기에 사용된 using은 단순 지시자의 용도로서 참조된 라이브러리의 네임스페이스(namespace)를 명시함으로써 이후 사용할 때 전체 한정을 벗어나 사용할 수 있게 된다. 네임스페이스(namespace) using은 이후 다시 설명하게 될 것이니 여기서는 이 정도로만 알고 넘어가기로 하겠다.

5번째 라인은 현재 응용프로그램의 네임스페이스(namespace)를 지정하고 있는 구문이다. namespace라는 키워드를 이용하여 적고 있다는 것 정도만 보고 넘어가자.

7번째 라인부터 class가 시작된다. C#은 클래스로부터 프로그램이 시작되어서 클래스로 끝이 나게 된다. 프로그램의 진입점은 Main 메서드를 가진 클래스가 된다. 9번째 라인을 보면 Main 메서드가 선언되어 있는 것을 볼 수 있을 것이다. 실제 프로그램이 9번째 라인부터 시작한다고 보면 된다.

11번째 라인은 콘솔에 출력을 해주기 위한 구문이다. System.Console.WriteLine("Hello, C#.");를 보면 System 네임스페이스에 Console클래스에 WriteLine 메서드를 이용해서 Hello, C#.이라는 텍스트를 출력하고 있다.

12번째 라인은 콘솔 입력을 받기 위해 입력 대기하라는 구문이다.

11, 12번째 라인을 살펴보면 System.Console까지 공통적으로 사용되고 있는 것을 볼 수 있다. 11, 12번째 라인 모두 System 네임스페이스의 Console클래스를 이용하고 있다는 뜻으로 실제 1번째 라인을 보면 using 키워드를 이용해 using System;와 같이 선언해 놓음으로서 System을 생략하고 다음과 같이 11, 12번째 라인을 고쳐도 문제없이 실행되는 것을 볼 수 있을 것이다.

Console.WriteLine("Hello, C#.");

Console.Read();

 

/* 출처 */
inobae의 놀이터
http://blog.naver.com/inobae

제 1장 .NET Framework


1. 닷넷 프레임워크

  1.1 닷넷 프레임워크에 대한 소개

  많은 사람들이 .NET에 대해 얘기하지만 .NET은 프로그래밍 언어가 아니다. 비록 .NET이라는 환경을 위한 다양한 언어들 (C#.NET, VB.NET, VC.NET, 등)을 제공하지만, 엄격히 말해 .NET을 프로그래밍 언어라고 볼 수는 없다. 여기서 얘기하고자 하는 .NET은 공통된 환경(CLR, Common Language Runtime)과 공통된 라이브러리(FCL, Framework Class Libraries)를 제공하는 사용자 플랫폼이라고 보면 될 것이다. 이를 위해 CLI(Common Language Infrastructure)에서 실행 코드와 실행 환경에 대한 명세를 제공한다. 서로 다른 언어들과 플랫폼에서 상호 호환성을 유지하기 위해서는 각각의 언어들이 플랫폼과 는 독립적으로 동일한 형태의 형을 지원해야 한다. 이를 .NET Framework에서는 CTS(Common Type System)라는 형태로 지원하고 있다. 자바의 JVM(Java Virtual Machine)과 비슷하다고 보면 된다. CTS은 다양한 형태의 형들에 대한 지원을 지원한다. CTS는 다양한 언어들의 컴파일러에 의해 공유되며, 공통된 형을 통해 다양한 언어들에 대한 통합, 형 안전성, 실행코드들의 성능을 향상 시킨다.

  CLI는 CTS에 정의된 형들에 대한 참조를 위해 메타 데이터를 사용한다. Metadata는 프로그램 언어에 독립적으로 저장된다. 그런 이유로, 메타데이터는 컴파일러나 디버그와 같은 툴과 VES(Virtual Execution System)사이에 교환 메커니즘을 제공한다. VES는 메타데이터와 수행가능한 관리 코드들에 대한 수행과 로딩을 담당하며, CLI기반의 언어들과 VES와 메타데이터들을 이용해서 통신이 가능하다.


  1.2 닷넷 프레임워크의 장단점

 


2. 닷넷 프레임워크 3.0

  닷넷 프레임워크 3.0은 .NET Framework 2.0에 WPF(Windows Presentation Foundation), WCF(Windows Communication Foundation), WF(Windows Workflow Foundation), CardSpace의 4가지 기술이 결합 확장된 형태라고 보면 된다. 닷넷 프레임워크 3.0를 이용하기 위해서는 각각의 프레임워크 SDK와 Visual Studio .NET 2005의 확장 플러그인 형식으로 제공되는 파일을 다운받아 이용할 수 있다. 여기서는  WPF, WCF, WF, CardSpace에 대한 각각의 소개는 다음으로 미루기로 하겠다.


3. C#에 대한 소개

  C#은 ECMA-334와 ISO/IEC 23270에 의해 표준화 되어 있으며, Microsoft의 C# 컴파일러는 이 표준에 따라 작성되었으며, 닷넷 프레임워크 하에서 돌아간다. C#은 C, C++, Java, Delpi와 같은 기존 프로그래밍 언어들에 영향을 받아 디자인 되었다. 현재는 닷넷 프레임워크 3.0이 릴리즈 되어 기존의 1.1에 비해 많은 API와 개선된 성능을 제공하고 있다. 닷넷 프레임워크 3.0은 기존의 닷넷 프레임워크 2.0 기반에 소개된 몇가지 구성요소들이 추가되어 있는 형태로서 닷넷 프레임워크 2.0을 그대로 이용하고 있다.

  C#의 장점은 닷넷 프레임워크의 장점을 그대로 가진다. 또한, 기존의 시스템들과의 통합된 개발이 가능하게 확장되어 있다.


4. Visual Studio .NET 2005

  4.1 Visual Studio .NET 2005 소개

  2005년 영문판을 정식 출시하면서 그 모습을 드러냈다. 한글판은 2006년 3월 출시 되었다. Visual Studio .NET은 개발자들이 .NET Framework하에서 돌아가는 프로그램을 개발하기 위한 통합 개발환경이다. 2005 버전에서는 다음과 같은 형태의 제품군들이 있다.

[표 1)] Microsoft VS .NET 2005 제품군 특징(MSDN 사이트 참조)

형태

Express

Edition

Visual

Studio

Standard

Edition

Visual

Studio

Professional

Edition

Visual

Studio

Tools For

Office

Visual

Studio

Team

System

IntelliSense

Yes

Yes

Yes

Yes

Yes

Code editor

Yes

Yes

Yes

Yes

Yes

Code snippets

Yes

Yes

Yes

Yes

Yes

언어 지원

VB, VC#,

VC++, VJ#

과 같은 단일 언어 지원

All

All

VB와 VC#

All

Office

개바 지원

No

No

No

Office 제품군

개발 지원

Office 제품군

개발 지원

사용자 지원

기본 옵션과

메뉴

기본 옵션과

메뉴

Full

Full

Full

윈폼 디자이너

VB, VC#,

VC++, VJ#

Yes

Yes

Yes

Yes

웹폼 디자이너

Visual Web

Developer

Yes

Yes

Yes

Yes

모바일 디바이스

지원

No

Yes

Yes

No

Yes

데이터베이스

디자인 도구

(테이블과

Stored

Procedure

생성/수정)

Local

Local과

Remote

Local과

Remote

Local과

Remote

Local과

Remote

데이터 엑세스

디자이너

Local

Local과

Remote

Local과

Remote

Local과

Remote

Local과

Remote

문서

기본 문서

MSDN

MSDN

MSDN

MSDN

클래스 디자이너 와 객체 테스트

No

Yes

Yes

Yes

Yes

XML 에디터

지원t

No

No

Full XML/

XSLT

Full XML/

XSLT

Full XML/

XSLT

배포 툴

Click Once

Click Once

Yes

Yes

Yes

소스 제어

No

MSCCI 호환

(Visual SourceSafe 별도 판매)

MSCCI 호환

(Visual SourceSafe 별도 판매)

MSCCI 호환

(Visual SourceSafe 별도 판매)

MSCCI 호환

(Visual SourceSafe 포함,

VS Team

Foundation Server 따로 판매)

디버깅

Local

Local

Local/

Remote

Local

Local/

Remote

64비트 컴파일러

지원(Itanium)

No

No

No

No

Yes

64비트 컴파일러 지원(X64)

No

Yes

Yes

Yes

Yes

서버 탐색기

No

No

All

All

All

SQL Server

2005 통합

No

No

Yes

Yes

Yes

Code Profiling

No

No

No

No

Yes

Static Analysis

No

No

No

No

Yes

단위 테스트

No

No

No

No

Yes

프로젝트 관리

No

No

No

No

Yes

Test Case

관리

No

No

No

No

Yes


  Visual Studio 외에도 .NET 개발환경으로서 Sharp Developer이라는 무료 IDE 환경이 존재한다. 여기서는 Visual Studio를 이용한 개발을 소개하며 따로 다른 IDE 환경에 대한 소개는 언급하지 않겠다.



/* 출처 */
inobae의 놀이터
http://blog.naver.com/inobae