[C# 기초] 4. 클래스(Class)
C# 언어도 객체지향 언어 중 하나이므로 객체지향 프로그래밍에서 가장 중요한 특징인 클래스(Class) 가 무엇인지 배웁니다.
클래스(Class)
클래스를 이야기 하기전에, 객체지향이 무엇인지 먼저 짚고 가겠습니다.
객체지향 프로그래밍(Object Oriented Programming)은 모든 것을 객체 단위로 표현하는 프로그래밍 패러다임입니다. 이 방법으로 프로그래밍할 경우 프로그램이 단순화되고, 생산성과 신뢰성이 높은 시스템을 구축할 수 있다고 합니다. 시간이 지날수록 프로그래밍언어가 발전하면서 생산성이 아주 좋아졌는데 프로그래밍할 때의 아이디어(알고리즘)은 항상 중요하지만, 이 아이디어를 어떻게 단순화시켜 유지보수(확장성)를 얼마나 쉽게 할 수 있는가가 매우 중요하다고 합니다.
클래스는 복합데이터 형식입니다. 데이터와 메소드를 묶은 또 하나의 데이터 형식인 것입니다. 클래스는 클래스 안에 선언된 변수(데이터)와 이 변수를 다루는 메소드 들의 집합입니다. 클래스는 기본적으로 생성자(Constructor)와 소멸자(Destructor)라는 2가지 메소드가 존재하는데 생성자는 클래스가 만들어질때 호출되는 메소드이고 클래스가 사라질때 소멸자가 호출됩니다.
하지만, C#에서는 CLR의 가비지 컬렉티가 객체 소멸 시점을 판단해서 소멸자를 호출하기 때문에 사용하지 말것을 권합니다.
클래스로 인해 객체지향프로그래밍의 중요한 3가지 특징으로 은닉성, 상속성, 다형성이 있는데 하나씩 알아보도록 하겠습니다.
은닉성(Encapsulation), 공개수준 결정하기
클래스는 사용자에게 필요한 최소의 기능만 노출하고 내부를 감출 것을 요구합니다.
C#은 다음과 같은 5가지 한정자를 제공합니다.
접근 한정자 (Access Modifier) | 설명 |
---|---|
public | 클래스의 내부/외부 모든 곳에서 접근할 수 있습니다. |
protected | 클래스의 외부에서는 접근할 수 없지만, 파생 클래스에서는 접근이 가능합니다. |
private | 클래스의 내부에서만 접근할 수 있습니다. 파생 클래스에서도 접근이 불가합니다. |
internal | 같은 어셈블리에 있는 코드에 대해서만 public으로 접근할 수 있습니다. 다른 어셈블리에 있는 코드에서는 private 와 같은 수준의 접근성을 가집니다. |
protected internal | 같은 어셈블리에 있는 코드에 대해서만 protected로 접근할 수 있습니다. 다른 어셈블리에 있는 코드에서는 private 와 같은 수준의 접근성을 가집니다. |
은닉성이라는 개념때문에 보통 필드를 private로 선언하고, public 인 GetData(), SetData() 형식의 메소드를 만들어 사용합니다. 아주 단순하고 반복적인 코딩이죠. 또 데이터를 얻고 쓰기 위해 메소드를 사용해야 하는 것이 불편합니다. 하지만 C#에서는 프로퍼티 (Property)를 사용합니다.
프로퍼티(Property), 은닉성과 편의성 동시에 잡기
프로퍼티는 데이터의 오염에 대해선 메소드처럼 안전하고, 데이터를 다룰 때는 필드와 같으므로 간편합니다.
코드 1:
class Account
{
private int Money;
public int GetMoney()
{
return this.Money;
}
public void SetMoney(int Money)
{
this.Money=Money;
}
}
코드 2:
class Account
{
private int money;
public int Money
{
get { return money; }
set { money= value; }
}
}
코드 3:
class Account
{
public int Money { get; set; }
}
위 세가지 코드는 전부 같은 역할을 합니다.
1번째 코드를 C#에서는 2번째 방식인 프로퍼티를 사용하고, 심지어 3번째처럼 자동 구현 프로퍼티라는 것을 사용합니다. 그 뿐만 아니라, 첫번째 코드는 Money에 100을 set하기위해서 SetMoney(100); 읽기 위해서 GetMoney() 메소드를 이용하지만, 2번째와 3번째 같은 프로퍼티는 Money=100; 와 같이 프로퍼티를 변수처럼 사용하기만 하면 됩니다.
2번째 프로퍼티부터 보겠습니다. value는 set 접근자의 암묵적 매개변수이므로 매개변수로 받은 값을 money에 할당합니다. 현재 get과 set이 있지만, 프로퍼티를 쓰기 전용, 읽기 전용으로 만들고 싶을 때는 각각 set 과 get만 정의하면 됩니다.
3번째는 자동 구현 프로퍼티라고 합니다. 프로퍼티도 계속 해서 필드마다 단순 반복으로 만들다 보니 C#은 자동적으로 내부에 필드를 구현해줍니다. 따라서 이와 같은 코드가 가능해졌습니다.
프로퍼티를 이용하여 객체를 생성할때 각 필드를 초기화하는 방법이 있습니다.
클래스이름 인스턴스 = new 클래스이름 ()
{
프로퍼티1 = 값,
프로퍼티2 = 값
}
코드에서 살펴보겠습니다.
class MyInfo
{
public string Name { get; set; }
public int Age { get; set; }
public string Job { get; set; }
public void WhoAmI()
{
Console.WriteLine("Name: {0}", Name); // Name : 김현진
Console.WriteLine("Age: {0}", Age); // Age : 0
Console.WriteLine("Job : {0}", Job); // Job : 학생
}
}
class Program
{
static void Main(string[] args)
{
MyInfo me = new MyInfo()
{
Name = "김현진",
Job = "학생"
};
me.WhoAmI();
}
}
초기화 방법을 명명된 매개변수처럼 순서에 상관없이, 원하는 데이터만 초기화를 할 수 있습니다. (C#에서 초기화하지 않은 변수는 디폴트 값으로 초기화됩니다)
중첩 클래스 (Nested Class)
중첩 클래스는 클래스안에 선언되어있는 클래스입니다.
중첩 클래스는 자신이 소속되어 있는 클래스의 멤버를 자유롭게 접근가능합니다. 클래스 외부에 공개하고 싶지 않은 형식을 만들 고 싶을 때, 현재 클래스의 일부분처럼 표현하는 클래스를 만들고자 할때 사용할 수 있습니다. 다른 클래스의 private 멤버에도 마구 접근가능하여 은닉성을 무너뜨리기도 하지만, 장점을 훨씬 더 부각시킬 수 있다면 사용하는 것이 좋겠습니다.
분할 클래스 (Partial Class)
분할 클래스는 여러번에 나눠서 구현하는 클래스를 말합니다.
클래스 하나를 개발하는 데도 여러명이 해야할 경우 다른 파일에서 정의해도 컴파일할 때 묶어서 하나의 클래스로 컴파일합니다. class 앞에 partial 키워드만 붙이면 됩니다. (물론, 클래스 이름은 같아야 합니다)
확장 메소드 (Extension Method)
기존 클래스의 기능을 확장하는 방법입니다.
상속받아 기능을 추가하는 것이 아닌, 기존 클래스의 기능을 확장합니다.
public static class 클래스이름
{
public static 반환형식 메소드이름 ( this 확장하고자하는 클래스 또는 형식 , 매개변수 . . .)
{...}
}
그림에서 처럼 기존 int 라는 클래스의 메소드들 외에 Square()가 확장된 것을 확인하실 수 있습니다.
구조체(Structure)
C#의 복합 데이터형식에는 클래스 말고도 구조체(Structure) 가 있습니다.
Class 뿐만 아니라 C언어에서 자주 접하던 Struct도 자주 사용됩니다. 클래스와 구조체의 차이점을 보겠습니다.
특징 | 클래스 | 구조체 |
---|---|---|
키워드 | class | struct |
형식 | 참조형식 | 값 형식 |
복사 | 얕은 복사 (Shallow Copy) | 깊은 복사 (Deep Copy) |
인스턴스 생성 | new연산자와 생성자 필요 | 선언만으로도 생성 |
생성자 | 매개변수 없는 생성자 선언가능 | 매개변수 없는 생성자 선언 불가능 |
상속 | 가능 | 모든 구조체는 System.Object 형식을 상속하는 System.ValueType 으로부터 직접 상속받음 |
가장 큰 차이점은 클래스는 참조 형식이고 구조체는 값 형식 입니다.
이것이 무엇을 의미하냐면, 구조체의 인스턴스는 스택에 할당되고 사용이 끝나면 즉시 메모리 상에서 사라집니다. 힙을 사용하지 않기 때문에 성능 상에서 많은 이점을 가질 수 있습니다. 아무리 컴퓨터 메모리가 커졌다고 해도, 최적화 문제는 항상 고려해야 합니다. 예를 들면 3차원 그래픽을 구현하는 데 있어서 백만개의 점의 데이터를 갖고 있다고만 해도 클래스를 사용하는 것과 구조체를 사용하는데 많은 성능 상의 차이가 발생합니다.
참고로 C#에서는 변수를 선언한 후 초기화 하지 않을 경우 기본값으로 CLR이 자동으로 초기화해줍니다.
struct Point3D
{
public int X;
public int Y;
public int Z;
public Point3D(int X, int Y, int Z)
{
this.X = X;
this.Y = Y;
this.Z = Z;
}
public override string ToString() // System.Object 를 항상 상속받습니다.
{
return string.Format("({0},{1},{2})", X, Y, Z);
}
}
class Program
{
static void Main(string[] args)
{
Point3D point1; // 선언만으로도 인스턴스 생성합니다.
point1.X = 10;
point1.Y = 20;
point1.Z = 30;
Console.WriteLine(point1.ToString()); // <10, 20, 30>
Point3D point2 = new Point3D(11, 21, 31); // 생성자를 이용하여 인스턴스 생성도 가능합니다.
Point3D point3 = point2; // 구조체는 깊은 복사가 이루어집니다.
point3.Z = 50;
Console.WriteLine(point2.ToString()); // <11, 21, 31>
Console.WriteLine(point3.ToString()); // <11, 21, 50>
}
}
클래스는 객체지향의 핵심 개념으로 반드시 이해하고 있어야하는 내용이므로 이 포스팅 뿐만 아니라 다양한 문서로 읽어보고, 직접 코드를 작성해보시기를 권장드립니다.객체지향의 중요한 특징인 은닉성, 상속성, 다형성 중 은닉성만 알아보았는데요. 다음 포스팅에서 상속과 다형성에 대해 정리하겠습니다.