C# 클래스 기초 – C# 초급 9

C# 클래스 기초

클래스를 중심으로 프로그래밍 하는 객체지향 프로그래밍은 지금은 대세 방법론이지만 2000년대 이전에 자바가 인기를 끌기 전까지는 생소한 단어였습니다. C언어의 시대는 데이터와 함수를 중심으로 절차적 프로그래밍 방식을 사용했습니다.

지금 과거를 돌아 보면 C언어 프로그래밍은 상당히 어려운 일이었다고 생각이 듭니다. 객체지향 프로그래밍에서 처럼 접근 제어자가 없고 전역변수를 마음대로 사용해서 프로그램의 규모가 커질 수록 디버그 시간은 계속 늘어나는 구조입니다. 어떤 프로그램이건 버그가 참 많았었던 기억이 납니다. 그래서 버그 없이 간결한 코드의 C 프로그램을 만드는 사람은 존경도 많이 받았습니다.

지금은 개발 환경과 런타임 환경(운영체제)의 안정성이 높아지면서 앱의 오류같은 것은 많이 줄었는데요. 이 과정에서 객체지향 프로그래밍(OOP – Object Oriented Programming 이라고 함) 기술의 보급도 상당한 역할을 했습니다.

클래스 OOP

클래스(class)는 객체지향 프로그래밍의 핵심입니다. OOP 앱 개발이란 클래스로 시작해서 클래스로 끝난다고 해도 과언이 아니지요.

문제는 초보 학습자가 개념을 잡는데 시간이 좀 걸립니다. OOP는 프로그래밍 언어가 아니라 방법론이라서 원래는 OOP를 배우면서 객체지향 언어인 Java, C#, C++의 실습을 같이 하는게 맞는데 이 포스팅은 C#과 .NET의 시리즈 포스팅이라서 이론을 깊이 설명하지는 않습니다.

개인적으로 OOP를 배우기에 가장 좋은 언어는 Java 라고 보는데요. 100% OOP를 추구하는 언어이고 아무래도 한국에는 C#보다 Java 를 많이 사용하다 보니 상대적으로 Java 쪽에 교재와 강의가 많습니다. (C#이 부적합하다는 것은 아니지만) OOP강의는 남궁성 님의 자바의 정석을 추천합니다. 자바 입문서이지만 지면의 상당부분을 객체 지향 개념을 설명하는데 포커스를 맞춰서 초보자들도 이 교재를 한 두세번 정도 학습하면서 객체 지향 개념을 다 잡는다고 합니다. 저자 남궁성님 유튜브에는 교재 강의가 공개되어 있습니다.

C# 초급에서 Java를 언급하는 것은 조금 맞지는 않지만 프로그래밍을 하다 보면 기초 단계는 거의 다 비슷하기 때문에 OOP를 자바로 배우나 C#으로 배우나 원리는 같습니다.

클래스란?

클래스라는 단어는 익숙한 단어입니다. 학교에서 학급을 클래스(class)라고 하고, MMORPG 게임에서 캐릭터의 종족이나 직업을 클래스(class)하고 합니다. 일반적으로 사용되는 클래스의 용어는 다양한데 프로그래밍에서 클래스는 객체 지향 프로그래밍에서 멤버 변수와 멤버 메소드를 정의하는 템플릿(틀)입니다. C# 초급에서 타입(type)에 대해 알아봤는데 클래스는 사용자가 정의하는 타입에 속합니다.

클래스의 멤버 변수와 멤버 메소드는 접근 제어자 등으로 서로 밀접한 연관이 되어 있습니다. 절차적 프로그래밍에는 클래스가 없고 데이터와 함수만 있습니다. 데이터를 관리를 해야 하는데 전역 변수를 여러 함수가 돌려쓰기도 하면서 데이터의 보호가 어려워집니다. 반면 클래스는 데이터에 접근할 수 있는 권한을 제한하여 애초에 가능성을 차단합니다. 이런 방식은 규모가 큰 프로젝트일 수록 빛을 발합니다. 이것을 캡슐화라고 합니다. 캡슐화, 상속, 다형성은 OOP의 중요한 특징이고 C#에는 이것들이 잘 구현되어 있습니다.

이 세가지 개념이 한번에 이해하기에는 벅찬 내용들입니다. 이론과 함께 하나씩 프로그래밍 실습을 하다보면 차츰 이해가 됩니다.

클래스의 장점으로는 현실 세계의 문제를 컴퓨터 프로그래밍으로 옮기기가 좋습니다. 그게 물질적인 것이든 정신적인 것에 상관없이 말만 되면 뭐든지 클래스로 만들 수 있습니다. 예를 들어 컵이라는 현실의 사물을 클래스로 만들어 보면… 컵은 종류가 있고, 높이, 무게도 있습니다. 컵에 그림이나 글씨가 있기도 합니다. 컵에 들어있는 내용물도 있습니다. 이런 것들은 클래스의 멤버 변수로 표현할 수 있습니다. 또 컵은 고유한 동작이 있지요. 컵에 물을 따를 수 있고, 물을 마실 수 있습니다. 커피를 따를 수도 있고 더러워지면 세척을 하기도 합니다. 이것들은 클래스에 속한 멤버 메소드입니다.

컨셉을 코드로 옮겨보면 아래와 같습니다. 뭐가 길게 있지만 특별한 것은 없습니다. 속성과 메소드 그리고 접근 제어자를 적당히 배치한 것에 불과합니다. 당장 모든 내용을 이해할 필요는 없습니다. 이런 와꾸로 클래스를 선언하고 사용한다는 정도로 충분합니다.

using System;

namespace Smoothie
{
    class Program
    {
        static void Main()
        {
            Cup myCup = new Cup();
            myCup.getWater();
            myCup.drinkWater();
            myCup.washCup();
            myCup.display();
        }
    }

    class Cup
    {
        string type = "기본컵";
        double height = 10.5;
        double radius = 7.7;
        double weight = 50;
        string text = "별다방";
        Image img = new Image();

        public void getWater()
        {
            Console.WriteLine("컵에 물을 따른다");
        }
        public void drinkWater()
        {
            Console.WriteLine("물을 마시다");
        }
        public void washCup()
        {
            Console.WriteLine("컵을 세척하다");
        }
        public void display()
        {
            Console.WriteLine("---------------------- ");
            Console.WriteLine($"컵의 종류: {type}");
            Console.WriteLine($"높이:  {height}");
            Console.WriteLine($"지름:  {radius}");
            Console.WriteLine($"무게:  {weight}");
            Console.WriteLine($"글씨:  {text}");
            Console.WriteLine($"이미지:  {img.imageFile}");
        }
    }

    class Image
    {
        public string imageFile = "사과나무 그림";
    }
}
[실행결과]

컵에 물을 따른다
물을 마시다
컵을 세척하다
----------------------
컵의 종류: 기본컵
높이:  10.5
지름:  7.7
무게:  50
글씨:  별다방
이미지:  사과나무 그림

첫 단계에서 클래스를 선언하고 인스턴스를 생성하는 것을 확실히 이해하는 것이 좋습니다. 프로그래밍은 기초가 중요해서 앞단계를 확실히 이해못하면 다음 단계에서 계속 걸리기 때문에 계속 돌아오게 됩니다. 한가지 개념을 학습하더라도 확실하게 이해하는게 중요합니다.

위의 코드는 말로 설명한 컵에 대해서 대략적으로 코드로 표현해 봤습니다. 이미지 파일을 함께 사용하면 컵 프로그램도 만들 수 있을 것 입니다.(실용성은 없지만…) 약간 게임 같지요.

객체 지향 프로그래밍은 게임 개발에 당연히 사용되구요. 플레이어 캐릭터 적 캐릭터도 다 클래스로 만듭니다. 클래스를 사용하면 상당히 간단히 만들어 낼 수가 있습니다. class Player{ }, class Enemy { } 이 안에 체력을 HP 멤버 변수로 놓고 메소드에는 공격하다, 회피하다, 공격받다 등 게임에 필요한 기능을 구현해주면 됩니다.

Player 캐릭터 객체가 Enemy를 공격하려면 Player 인스턴스의 공격하다() 메소드를 호출해주면 되고 공격을 받는 쪽에 피해가 발생하면 공격받다() 메소드를 실행시켜서 Enemy 객체의 피해를 계산하면 될 것 입니다. 이렇게 객체들의 상호 작용에 의해서 프로그래밍이 돌아가는 것이 OOP 입니다.

클래스 선언

그럼 클래스를 선언하는 방법을 알아보겠습니다. class 키워드를 쓰고 클래스의 이름이 나오고 { } 중괄호안이 클래스 내부 영역입니다.

    class MyClass
    {

    }

다음은 멤버 변수와 메소드를 작성합니다. 멤버 변수는 기본형 타입이나 사용자 정의 타입도 될 수 있습니다. byte, int, double 모두 가능하고 클래스도 멤버로 넣을 수 있습니다.

메소드는 객체가 사용하는 함수입니다. public 이란 접근 제어자가 붙었는데 public 은 다른 클래스에서 접근이 가능합니다. 접근제어자가 없다면 default 값으로 private 이기 때문에 외부 클래스에서 접근이 불가능합니다. 이 메소드를 호출하려면 public 이어야 합니다.

    class MyClass
    {
        int var1;
        
        public void showMember()
        {
            Console.WriteLine($"var1 : {var1}");
        }
    }

클래스를 선언하는 것은 세가지가 필요합니다. 첫째 클래스 이름 그 다음은 클래스의 멤버 변수 그리고 메소드입니다. 클래스 선언(Declaration)과 정의(Definition) 둘을 혼용해서 사용하는데 클래스를 선언하는 것이 곧 그 내용을 정의하는 것이기 때문입니다.

멤버 변수 (필드)

멤버 변수는 필드(field)라고도 합니다. 객체 지향은 같은 것을 맥락에 따라 여러가지 이름으로 부르기 때문에 용어 사용에 익숙해져야 이해가 빠릅니다. 필드에는 기본 타입뿐만 아니라 어떤 타입도 올 수 있습니다. 초기화를 하지 않으면 기본값(default)이 할당됩니다.

using System;

namespace Smoothie
{
    class Program
    {
        static void Main()
        {
            MyClass m1 = new MyClass();
            m1.showMember();
        }
    }
    class MyClass
    {
        int var1;
        int var2 = 52;
        
         public void showMember()
        {
            Console.WriteLine($"var1 - default : {var1}");
            Console.WriteLine($"var2 - initialized : {var2}");
        }
    }
}

[실행 결과]
var1 - default : 0
var2 - initialized : 52

메소드

메소드는 클래스의 함수입니다. 사용법은 함수와 같은데 첫번째 함수의 이름, 매개변수 리스트(매개변수가 없을 수도 있다), 반환타입(Return type) 함수의 몸통인 중괄호 { } 블록에는 실행 코드가 들어갑니다.

위의 showMember 메소드처럼 반환값이 없을 때 void 타입을 사용합니다. 메소드는 오버로딩 등 OOP에 있어서 중요한 포인트가 많이 있습니다. 자세한 내용은 해당 포스팅에서 알아보고 여기서는 클래스를 구성하는 두 가지 중의 하나다 정도로 설명합니다.

클래스 참조 변수와 인스턴스

위에서 클래스를 만드는 기본 문법에 대해서 봤습니다. 이 와꾸에 살을 붙여 나가면 더 복잡한 클래스도 만들 수 있을 겁니다.

그렇다면 클래스를 사용한다는 것은 무슨 말 일까요? 클래스는 일종의 빵틀같은 것이라고 했습니다. 설계도, 청사진이라고도 말합니다. 빵틀은 빵과 다릅니다. 빵틀이 있다고 해서 빵을 먹을 수는 없습니다. 자꾸 당연한 말을 하지만 당연한 것을 컴퓨터 안에서 이해하는 것이 생소할 수도 있습니다. 이제 빵을 찍어내고 구워서 먹어야겠습니다. 빵틀을 클래스라고 하면 빵틀에서 나온 빵은 인스턴스(instance) 혹은 객체(object) 라고 합니다. 그런데 클래스는 int 같은 기본 타입과는 다른 참조형 타입(reference type)입니다.

참조형 타입은 값을 직접 가지고 있는게 아니라 클래스의 참조, 즉 주소와 타입 정보를 가지고 있습니다.

앞서 만든 MyClass 변수를 선언해보겠습니다. 아래의 m1 은 MyClass 타입의 참조입니다. Main() 에서 선언하면 스택 메모리에 참조를 저장합니다.

MyClass m1;

참조 변수를 선언했다면 그 내용을 채울 MyClass 타입 데이터가 필요합니다. 참조 변수를 선언하면 초기화는 null 값이 들어갑니다. 이것으로는 아직 사용할 수가 없습니다.

다음 코드에서 MyClass 타입 인스턴스를 생성합니다. 클래스는 new 연산자로 힙메모리를 사용합니다. 인스턴스는 클래스라는 틀을 이용해서 실제 메모리에 생성한 객체입니다. m1 은 클래스 참조 변수이고 여기에 인스턴스를 할당해서 사용할 수 있습니다. m1 에서 public 으로 접근 가능한 변수와 메소드를 도트 연산자(.)로 사용합니다. [ex: m1.메소드( ) ] new 연산자는 C++ 부터 사용되던 방식인데 C언어에서 메모리를 할당하던 malloc 함수가 객체 지향 프로그래밍에 맞게 변화한 문법입니다. C++ 은 delete 명령으로 메모리를 직접 해제해야 하지만 C#에서는 GC(Garbage Collector)가 알아서 해제를 합니다.

C언어 -> C++ -> C# 으로 메모리 사용법이 달라졌습니다. (과거의 언어가 성능이 떨어지는 개념이 아니라 목적이 다른 것) 클래스를 객체지향 개념에서 이해하는게 우선되야 하지만 클래스 할당 시 메모리 안에서 일어나는 과정도 알고 있어야 합니다.

MyClass m1;
m1 = new MyClass();

인스턴스(instance)

빵틀로 여러개의 빵을 찍어내는 것 처럼 인스턴스도 여러개를 찍어낼 수 있습니다. 아니 이론적으로 무한대로 찍어낼 수 있습니다. 컴퓨터가 인스턴스를 만들어내는 속도는 빵틀로 찍어서 굽는 시간보다는 많이 빠르니까 컴퓨터는 생각보다 많은 것을 할 수 있습니다.

아래처럼 m1, m2, m3 … mN 은 독립된 각각의 인스턴스를 메모리 공간에 생성합니다. 이들은 GC가 해제하기 전까지 힙메모리에 상주합니다. 프로그래머는 이들 하나하나를 객체로써 인식하고 프로그램을 설계할 수 있습니다. 어떤 인스턴스를 몇개 생성할 것인가? 인스턴스의 지속시간은 얼마나 되는가? 이런 질문들이 실제적으로 중요한 문제가 됩니다.

MyClass m1 = new MyClass();
MyClass m2 = new MyClass();
MyClass m3 = new MyClass();

접근제어자(Access Modifiers)

C#의 접근제어자는 private, public, protected, internal, protected internal 의 다섯개가 있는데 초장부터 너무 많으니까 일단 처음에 알아야할 private 과 public 에 대해서 알아보겠습니다.

private 은 프라이빗, 프라이버시의 그 private 입니다. 개인적인 정보라 알려주지 않겠다는 겁니다.

public 은 private 과 반대로 공공에 오픈하는 것 입니다. 누구나 접근하면 사용할 수 있습니다.

예를 들어 Person 이라는 class 를 만들어서 사람을 표현한다고 가정합니다. 이름은 누구나 알 수 있게 public 을 사용할 수 있습니다(이름도 안 알려 줄 수 있지만) 그런데 주민번호는 민감한 정도가 들어가므로 알려줄 수 없습니다. private 을 사용하면 외부에서 접근이 차단됩니다.

    class Person
    {
        public String name;
        private long socialNumber;
    }

클래스를 설계할 때 필드의 특성에 따라 공개할 수도 안할 수도 있습니다. 프로그램 안에서 보면 객체간에 불필요한 상호작용을 최소화할 수 있기 때문에 안정성이 높아집니다. 전역변수 하나를 여러 함수에서 돌려쓰기 때문에 발생했던 많은 문제들이 해결됩니다.

접근제어자는 필드 뿐 아니라 메소드에도 적용되는데요. public 과 private 을 나누는 기준은 메소드에 관한 다른 포스팅에서 다루도록 하겠습니다.

요약

C# 클래스 기초를 둘러봤습니다. 하나의 포스팅에 너무 많은 내용을 담으려다 보니 다소 부실한 설명도 있습니다. 하루아침에 객체지향 프로그래밍과 클래스에 대해서 100% 이해하는 것은 어렵습니다. 대학이나 학원의 관련 강의를 들어봐도 진도가 그렇게 빠르지 않습니다. 개념을 잡는다는 것은 좋게 말하면 이해하는데 시간 투자를 하는 것이고 나쁘게 말하면 진도가 느리다는 것 입니다. 방법론이고 이론이기 때문에 이해도를 높이기 위해서는 상당히 시간을 많이 투자해야 합니다.

물론 이해하는 것은 중요합니다. 하지만 시간도 마찬가지로 중요한 자원이기 때문에… 해서 이번에는 조금 빠르게 넘어가는 포스팅을 해봤습니다. 인터넷은 좋은 문서와 자료가 넘치기 때문에 각각의 주제에서 나오는 추가 의문점들은 검색을 통해 답을 얻는 것도 좋은 방법입니다. 아쉬운 것은 한글 검색에는 자료가 한계가 있고 영문 웹사이트와 pdf 쪽에 좋은 자료가 많습니다.

참고문서

C# Heap(ing) Vs Stack(ing) In .NET – Part One (c-sharpcorner.com)

Leave a Comment