Favicon

Class type

Peponi11/19/202411m

C#
SyntaxTypeclass

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 멤버만 가질 수 있다. 또한 앞의 예시와는 달리, classstatic 한정자로 선언하게 되면 상속을 주고 받는 것이 불가능해진다.

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를 같은 어셈블리 안 여러 곳에 분산하여 정의할 수 있다. 쉽게 사용 가능하고, 용도에 따른 코드 분리가 가능해지기 때문에 강력하게 활용된다.

Fields.cs
public partial class PartialClass
{
    // Fields
 
    public int A { get; set; }
    public int B { get; set; }
    public int C { get; set; }
}
Methods.cs
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가 존재하는 경우, 다른사람이 쉽게 알아볼 수 있게 정리가 필요할 수 있다.
    • 이 시나리오에서는 가능하면 클래스를 분리하는 것이 더 좋은 방법일 것으로 생각된다.

4. 참조 자료