Class type
Peponi │ 11/19/2024 │ 11m
Class type
Peponi
1. Introduction
class는 필드, 메서드, 속성, 이벤트 등의 멤버가 포함될 수 있는 참조 형식이다. class
는 동명의 키워드를 사용하여 정의하며, 상속을 지원한다. 또한 각 class
는 하나의 클래스를 상속 받을 수 있고, 하나 또는 여럿의 인터페이스를 구현할 수 있다.
2. 포함 가능한 멤버
class
에는 다음 멤버를 사용할 수 있다.
멤버 | 내용 |
---|---|
생성자 | class 생성자초기화를 포함한 작업을 수행 |
상수 | 선언과 동시에 초기화 |
필드 | class 또는 struct 에서 선언되는 변수 |
소멸자 | 일부 dispose 불가 상황에서는 호출 안될 수 있음 |
메서드 | 모든 명령은 메서드 컨텍스트에서 실행 |
속성 | 읽기 / 쓰기 모드 및 한정자 설정 가능 데이터 멤버처럼 쓰지만 메서드이다 |
인덱서 | 사용자 정의 형식을 배열처럼 사용 가능하게 해준다 데이터 형식에서 많이 이용 |
연산자 | 데이터 형식에서 많이 이용 |
이벤트 | GUI SW에서 많이 활용Event based programming 의 핵심 요소 |
대리자 | 메서드를 캡슐화C++ 의 함수 포인터와 유사 |
클래스 | Nested class가 허용된다 |
인터페이스 | 구현해야 할 기능 대한 정의 |
구조체 | 값 의미 체계를 가지는 값 형식 |
열거형 | 특정 선택 집단 정의에 많이 이용 |
3. class 정의 및 사용
class
는 다음 내용과 같이 정의하고 사용할 수 있다. 먼저 간단한 정의와 사용법을 확인한 후, class
를 활용할 수 있는 예시를 알아본다.
3.1. 기본 class 정의
다음은 간단한 class
정의를 보여준다.
public class CartesianCoordinate
{
public double X { get; init; } // 자동 구현 속성
public double Y { get; init; }
public CartesianCoordinate(double x, double y) // 생성자
{
X = x;
Y = y;
}
public override string ToString() => $"{X}, {Y}"; // 메서드
}
C# 12 버전부터는 선언 시 record처럼 기본 생성자를 정의할 수 있다.
- 기본 생성자의 인수에 대해 자동으로 필드를 생성해주지는 않는다.
- 추가로 생성자를 정의하는 경우, 반드시 기본 생성자를 경유해야한다.
public class CartesianCoordinate(int x, int y)
{
public int X { get; init; } = x;
public int Y { get; init; } = y;
public CartesianCoordinate(int x) : this(x, default) { }
}
3.2. class 사용
앞서 정의한 class
를 사용하는 방법은 아래와 같다.
CartesianCoordinate coordinate = new(1, 2);
Console.WriteLine(coordinate); // 1, 2
3.3. class 상속
class
는 상속
을 주고 받을 수 있다. 상속을 통해 class
의 원형을 유지하면서 기능을 확장할 수 있다. 상속을 받으려면 class
이름 선언부에 :
기호와 함께 상속받을 클래스명을 넣어준다. class
를 상속 받는 것은 하나만 가능하다.
public class BaseClass {}
public class BaseClass2 {}
public class DerivedClass : BaseClass {} // OK
// public class DerivedClass : BaseClass, BaseClass2 {} // CS1721: 'DerivedClass' 클래스는 기본 클래스('BaseClass', 'BaseClass2')를 여러 개 포함할 수 없습니다.
상속을 받는 경우, base class의 멤버의 액세스 한정자에 따라 접근이 가능하다. 액세스 한정자
에 따라, 접근이 불가능한 멤버에 접근하는 경우 에러메시지 CS0122
가 나타나게 된다.
public class BaseClass
{
public int DataA { get; set; } // public은 현재 또는 상속 받은 class,인스턴스에서 접근이 가능하다.
protected int DataB { get; set; } // protected는 현재 또는 상속 받은 class에서만 접근이 가능하다.
private int DataC { get; set; } // private은 현재 class에서만 접근이 가능하다.
}
public class DerivedClass : BaseClass
{
public int GetBaseClassSum()
{
int sum = 0;
sum += DataA;
sum += DataB;
// sum += DataC; // CS0122: 보호 수준 때문에 'BaseClass.DataC'에 액세스할 수 없습니다.
return sum;
}
}
DerivedClass derived=new();
Console.WriteLine(derived.DataA); // 0
// Console.WriteLine(derived.DataB); // CS0122
// Console.WriteLine(derived.DataC); // CS0122
3.4. 인터페이스 구현
class
는 하나 이상의 interface를 구현할 수 있다. interface
를 구현하는 경우, 통일성 있게 접근하면서 다형성을 만들어낼 수 있다.
public interface IDataService
{
public T LoadData<T>(string path);
public void SaveData();
}
public class CSVData : IDataService
{
private CSV _data;
public CSVData()
{
_data = new();
_data = LoadData<CSV>("CSVData.csv");
}
public T LoadData<T>(string path)
{
T data = default;
// data loading...
return data;
}
public void SaveData()
{
// data saving...
}
}
public class YAMLData : IDataService
{
private YAML _data;
public YAMLData()
{
_data = new();
_data = LoadData<YAML>("YAMLData.yaml");
}
public T LoadData<T>(string path)
{
T data = default;
// data loading...
return data;
}
public void SaveData()
{
// data saving...
}
}
List<IDataService> datas = new List<IDataService>();
datas.Add(new CSVData());
datas.Add(new YAMLData());
foreach (var data in datas) data.SaveData();
3.5. static class
static은 컴파일 시점에 바인딩되는 것을 의미하는 한정자이다. 로그, 파일 로드와 같은 클래스를 공유 자원으로 활용하기 좋다. 그러나 인스턴스화 및 형식 사용이 불가하고, static
멤버만 가질 수 있다. 또한 앞의 예시와는 달리, class
를 static
한정자로 선언하게 되면 상속을 주고 받는 것이 불가능해진다.
public static class Log
{
public static void WriteLog(string message) => System.IO.File.AppendAllText(@"C:\Log\LogFile.txt", $"{DateTime.Now} - {message}");
}
Log.WriteLog("Log message");
3.6. sealed class
sealed 한정자는 class
의 상속을 방지할 수 있게 해준다. 이는 앞의 static class
와 비슷하나, sealed class
는 상속을 받을 수 있고 인스턴스 생성이 가능하다는 점에서 차이가 있다.
public class Item
{
public string Name;
public double Price;
}
public sealed class StockItem : Item // 상속을 받는 것은 가능하다.
{
public string Code;
}
// public class StockDetails : StockItem // CS0509 : 'StockDetails': sealed 형식 'StockItem'에서 파생될 수 없습니다.
// {
// public string Description;
// public bool NeedToBuy;
// }
StockItem item = new StockItem
{
Name = "Company",
Code = "AAA-001",
Price = 50,
};
3.7. abstract class
abstract 한정자는 대상 형식이 불완전함을 나타낸다.
- 인스턴스화가 불가하고 추상 멤버가 포함될 수 있다.
sealed
한정자와는 반대로 상속을 줄 수만 있다.- 기본 구현을 할 수 있으며 (virtual), 상속받는 클래스에서 재정의 또는 구현을 할 수 있다.
- 인터페이스와 마찬가지로 통일성 있게 접근하면서 다형성을 만들어낼 수 있다.
public abstract class AbstractBase
{
public string Message { get; set; } = string.Empty;
public abstract void ConsoleWrite(string message);
public virtual string? ConsoleRead() => Console.ReadLine();
}
public class AbstractDerived : AbstractBase
{
public override void ConsoleWrite(string message) => Console.WriteLine(message); // 반드시 override 해야한다.
public override string? ConsoleRead() => base.ConsoleRead(); // override는 선택. override 안할 시 추상클래스 정의 이용
}
AbstractDerived abstractTest = new();
abstractTest.Message = abstractTest.ConsoleRead() ?? "Read failed";
abstractTest.ConsoleWrite(abstractTest.Message);
3.8. partial class
partial 키워드를 사용하면 class
를 같은 어셈블리 안 여러 곳에 분산하여 정의할 수 있다. 쉽게 사용 가능하고, 용도에 따른 코드 분리가 가능해지기 때문에 강력하게 활용된다.
public partial class PartialClass
{
// Fields
public int A { get; set; }
public int B { get; set; }
public int C { get; set; }
}
public partial class PartialClass
{
// Methods
public int Sum() => A + B + C;
public override string ToString() => $"A = {A}, B = {B}, C = {C}";
}
PartialClass partialClass = new PartialClass { A = 1, B = 2, C = 3 };
Console.WriteLine(partialClass.Sum());
Console.WriteLine(partialClass);
WARNING
partial class
는 동일 어셈블리, 모듈 안에서만 유효하다. 여러 모듈에 존재할 수는 없다.- 편의성으로 인해 클래스가 비대해지는 경우가 발생할 수 있다. 이런 경우는 리팩터링이 필요하다.
partial class
안에nested partial class
가 존재하는 경우, 다른사람이 쉽게 알아볼 수 있게 정리가 필요할 수 있다.- 이 시나리오에서는 가능하면 클래스를 분리하는 것이 더 좋은 방법일 것으로 생각된다.