.NET/C#

[C#] C#과 OOP(Object-Oriented Programming) - 3. 필드(Field)

클리엘 2022. 6. 24. 11:46
728x90

3. Field

 

'자동차'는 자신만의 차량번호와 생산이 완료되 시장에 나오는 출고일이라는 날짜가 존재할 수 있습니다. 이 값을 다루기 위해 이전에 만든 Car클래스에서는 Field를 아래와 같이 추가합니다.

public class Car
{
    public string Number;
    public DateTime FDate;
}

예제에서는 Field에서 string과 DateTime형식을 사용했지만 Field를 만드는 데는 int나 Array 등 C#에서 다룰 수 있는 모든 형식뿐만 아니라 Car와 같은 자신이 직접 만든 형식까지도 사용할 수 있습니다.

 

● 접근 한정자

 

'접근 한정자'는 캡슐화 과정에서 Field를 어떻게 외부에 노출할 것인지를 지정하는 것입니다. 위 예제에서 Car클래스의 Number과 Color라는 Field에는 접근 한정자로 'public'이 지정되었습니다. 사실 접근 한정자를 아무것도 지정하지 않을 수도 있는데 이럴 경우 기본적으로 'private'한정자가 기본으로 적용되며 해당 Field에는 오로지 클래스 내부에서만 접근할 수 있는 상태가 됩니다.

 

접근 한정자로 사용할 수 있는 keyword는 아래와 같이 4가지정도가 있으며 서로 다른 keyword가 결합되어 해당 keyword별 특징을 그대로 적용하여 사용될 수 도 있습니다.

private 아무것도 지정하지 않을 경우 기본이 되며 클래스와 같은 Type의 내부에서만 접근이 가능합니다.
internal private과 같지만 같은 Assembly안에서도 접근할 수 있습니다.
protected private과 같지만 해당 Type을 상속하는 자식 Type에서도 접근할 수 있습니다.
public Type의 외부에서 자유롭게 접근할 수 있습니다.
internal protected internal과 protected를 동시에 적용합니다.
private protected private과 protected를 동시에 적용합니다. 이 한정자는 C# 7.2부터 사용가능합니다.

● Field에서 값 다루기

 

Car와 같은 Type이 만들어 지고 해당 Type의 인스턴스가 생성되었다면 아래와 같이 원하는 필드에 값을 설정하고 해당 값을 확인할 수 있습니다.

using mylibrary;

Car c = new();

c.Number = "1234";
c.FDate = new DateTime(2022, 5, 15);

Console.WriteLine($"차량번호 {c.Number}번은 {c.FDate:yyyy년 MM월 dd일}에 출고되었습니다.");
//차량번호 1234번은 2022년 05월 15일에 출고되었습니다.

혹은 다음과 같이 객체의 인스턴스를 생성함과 동시에 Field에 값을 초기화할 수도 있습니다.

Car c = new()
{
    Number = "1234",
    FDate = new DateTime(2022, 5, 15)
};

● enum 사용하기

 

Field에 값을 저장하는 경우 상황에 따라서는 제한된 값만을 설정하도록 해야하는 경우가 있습니다. 이런 경우 enum(열거형)을 사용할 수 있는데 예를 들어 선택 가능한 차량의 색상을 표현하고자 하는 경우 아래와 같이 enum을 정의하고

public enum CarColor
{
    Red,
    Blue,
    Yellow,
    White,
    Black
}

해당 Type에 정의한 enum형식의 Field를 추가하여

namespace mylibrary;
public class Car
{
    public string Number;
    public DateTime FDate;
    public CarColor MyColor;
}

위에서 열거된 색상만을 선택할 수 있도록 하는 것입니다.

using mylibrary;

Car c = new()
{
    Number = "1234",
    FDate = new DateTime(2022, 5, 15),
    MyColor = CarColor.Black
};

Console.WriteLine($"차량의 색상은 {c.MyColor}입니다.");
//차량의 색상은 Black입니다.

enum은 내부적인 값을 int형식으로 다루게 되며 0부터 순서대로 값을 할당합니다. 따라서 예제에서의 enum값중 Red는 0을 Yellow는 2의 값을 가지게 됩니다. 물론 필요하다면 이 규칙을 깨고 임의의 정수 값을 개별적으로 할당할 수 있습니다.

public enum CarColor
{
    Red,
    Blue,
    Yellow = 3,
    White,
    Black
}

예를 들어 Yellow에 3을 할당했다면 Yellow는 3의 값을 가지게 되며 White는 4, Black는 5의 순서로 값이 할당됩니다.

 

enum은 단일값이 아닌 여러 값을 결합하여 할당할 수도 있는데 이러한 동작은 enum에 [System.Flags] 어트리뷰트를 적용함으로써 가능합니다.

[System.Flags]
public enum CarColor
{
    Red = 1,
    Blue = 2,
    Yellow = 4,
    White = 8,
    Black = 16
}

예제에서는 각 요소의 값을 1, 2, 4, 8, 16으로 구분하여 할당하였습니다. 이는 여러개의 값이 지정되는 할당된 값이 겹치는 경우를 피하기 위해서입니다. 예를 들어 기존처럼 순서대로 값이 할당될 때 Red는 0이 되고 Blue는 1이 되는데 Red와 Blue가 동시에 할당되는 경우 0+1은 1이 되므로 값은 Blue로 결정되는 문제가 발생할 수 있습니다.

 

혹은 숫자대신 bit의 각 자릿수로 구분할 수 있도록 다음과 같은 방법으로도 요소에 값을 할당할 수 있습니다.

[System.Flags]
public enum CarColor
{
    Red = 0b_0000_0001,
    Blue = 0b_0000_0010,
    Yellow = 0b_0000_0100,
    White = 0b_0000_1000,
    Black = 0b_0001_0000
}

또한 enum은 기본적으로 int형식으로 유지되지만 필요하다면 정수형의 다른 형식을 지정할 수도 있습니다.

[System.Flags]
public enum CarColor : byte
{
    Red = 0b_0000_0001,
    Blue = 0b_0000_0010,
    Yellow = 0b_0000_0100,
    White = 0b_0000_1000,
    Black = 0b_0001_0000
}

고의적으로 높은 값을 설정하지 않는 이상 굳이 int형을 유지할 필요가 없으므로 메모리 절약을 위해 낮은 형식을 지정하여 사용하는 것입니다.

 

이 상태에서 여러 개의 enum값을 할당하는 경우에는 |로 구분하여 할당해야 합니다.

Car c = new()
{
    Number = "1234",
    FDate = new DateTime(2022, 5, 15),
    MyColor = CarColor.Red | CarColor.Blue
};

Console.WriteLine($"차량의 색상은 {c.MyColor}입니다.");
//차량의 색상은 Red, Blue입니다.

그리고 할당된 값은 , 로 분리된 문자열 값으로서 확인할 수 있습니다.

 

● Collection 사용하기

 

고속도로를 달리다 보면 뒤에 차량을 여러 대 싣고 운반하는 트럭을 볼 수 있습니다. 이런 상황을 가정해 Car에 다음과 같은 Field를 추가하였습니다.

public class Car
{
    public string Number;
    public DateTime FDate;
    public CarColor MyColor;
    public List<Car> Cars = new();
}

예제에서 사용된 generic List<T>를 사용하면 지정한 모든 형식을 정렬된 상태로 저장하고 다룰 수 있습니다. 또한 new()를 통해 collection을 List<Car>라는 형식으로 초기화하고 있는데 이는 현재 Cars는 null이므로 Add()와 같은 메서드를 통해 Item을 추가할 수 있도록 해야 하기 때문입니다.

 

List<Car>에서 <Car>는 'generics'이라고 하는 것으로 Collection을 강력한 형식(strongly typed)으로 만들기 위한 것입니다. Object를 사용하는 것과는 달리 이러한 방법은 코드의 성능과 정확성을 향상시 키 킬 수 있습니다.

 

위와 같이 생성된 Cars Field는 아래와 같이 Add() 메서드를 통해 요소를 추가할 수 있습니다.

Car c = new()
{
    Number = "1234",
    FDate = new DateTime(2022, 5, 15),
    MyColor = CarColor.Red | CarColor.Blue
};

c.Cars.Add(new Car() { FDate = new DateTime(2022, 1, 1), MyColor = CarColor.Black });
c.Cars.Add(new Car() { FDate = new DateTime(2022, 2, 1), MyColor = CarColor.White });

foreach(Car item in c.Cars)
{
    Console.WriteLine(item.MyColor);
}
//Black
//White

● static

 

이전에 만들어진 Car Library를 사용해 instance를 생성하면 각각의 instance에 해당하는 Field는 생성된 instance마다 서로 다른 값을 가질 수 있습니다.

using mylibrary;

Car sedan = new()
{
    Number = "1234",
    FDate = new DateTime(2022, 5, 15)
};

Car truck = new()
{
    Number = "5678",
    FDate = new DateTime(2022, 3, 10)
};

Console.WriteLine(sedan.Number);
Console.WriteLine(truck.Number);
//1234
//5678

그러나 클래스에서 Field를 static으로 정의하게 되면 해당 Field의 값은 모든 instance에서 공유할 수 있게 됩니다.

 

예를 들어서 위 예제의 경우 차량의 제조사를 명시하기 위한 필드를 만들게 된 경우 

namespace mylibrary;
public class Car
{
    public string? Number;
    public DateTime FDate;

    public static string? brand;
}

클래스를 통해 직접 값을 할당하게 되면 해당 값은 각 instance에서 가져와 공유할 수 있게 됩니다.

using mylibrary;

Car sedan = new()
{
    Number = "1234",
    FDate = new DateTime(2022, 5, 15)
};

Car truck = new()
{
    Number = "5678",
    FDate = new DateTime(2022, 3, 10)
};

Car.brand = "잘나가 자동차";

Console.WriteLine($"{Car.brand}의 {sedan.Number}번 자동차");
Console.WriteLine($"{Car.brand}의 {truck.Number}번 자동차");
//잘나가 자동차의 1234번 자동차
//잘나가 자동차의 5678번 자동차

위와 같이 static으로 정의된 것을 static member라고 합니다. member라는 말에서 알 수 있듯이 static은 Field뿐만 아니라 Method나 생성자(Constructor), Property 등 다양한 곳에서 적용될 수 있습니다.

 

● const

 

값이 바뀌지 않는 경우라면 Field에 const를 키워드를 붙여줄 수 있습니다.

namespace mylibrary;
public class Car
{
    public string? Number;
    public DateTime FDate;

    public const string brand = "잘나가 자동차";
}

const는 위 예제에서 처럼 선언과 동시에 값이 할당되며 이후부터는 할당된 값은 변경할 수 없습니다. 이는 컴파일 단계에서 const Field 자체가 할당된 값으로 바뀌기 때문입니다.

 

마이크로 소프트에서도 const를 직접 활용하고 있는데 예를 들어 int형 변수의 최댓값을 확인하는 System.Int32.MaxValue에서도 MaxValue를 const Field를 정의한 것을 확인할 수 있습니다.

 

● readonly

 

값이 불변하는 또 다른 방법으로 readonly를 사용할 수 있습니다.

namespace mylibrary;
public class Car
{
    public string? Number;
    public DateTime FDate;

    public readonly string brand = "잘나가 자동차";
}

readonly가 const와 다른 점은 사전에 미리 값을 할당할 필요가 없기 때문에 생성자나 매개변수 할당을 통해서

namespace mylibrary;
public class Car
{
    public Car(string _brand)
    {
        brand = _brand;
    }

    public string? Number;
    public DateTime FDate;

    public readonly string? brand;
}

필요한 초기값을 할당할 수 있다는 것입니다.

Car sedan = new(_brand: "잘나가 자동차")
{
    Number = "1234",
    FDate = new DateTime(2022, 5, 15)
};

참고로 모든 인스턴스에서 값을 공유하기 위해 readonly는 static으로도 선언될 수 있습니다.

 

● 생성자 초기화

 

필드는 Type이 정의될 때가 아닌 runtime에서 초기화가 필요할 수도 있습니다. 이런 경우 코드상에서 생성자를 통해 각 필드의 초기화를 진행할 수 있습니다. 생성자 초기화는 이미 readonly를 사용할 때 잠깐 언급했었는데 다음과 같이 할 수 있는 것입니다.

namespace mylibrary;
public class Car
{
    public Car()
    {
        FDate = DateTime.Now;
    }

    public string? Number;
    public DateTime FDate;
}

위 예제는 생성자를 통해 FDate값을 초기화하고 있습니다. 초기화 시간은 생성자가 실행되는 순간의 시간입니다.

using mylibrary;

Car sedan = new()
{
    Number = "1234"
};

Car truck = new()
{
    Number = "5678"
};

Console.WriteLine($"{sedan.Number}번 자동차의 출고일:{sedan.FDate}");
Console.WriteLine($"{truck.Number}번 자동차의 출고일:{truck.FDate}");
//1234번 자동차의 출고일:2022-05-02 오전 10:42:53
//5678번 자동차의 출고일:2022-05-02 오전 10:42:53

생상자는 객체를 new키워드를 통해 인스턴스화 할 때 실행되는 특수한 메서드로서 필요한 경우 각각의 여러 옵션을 가진 생성자를 다수 만들어두고 사용할 수도 있습니다.

public Car()
{
    FDate = DateTime.Now;
}

public Car(DateTime _FDate)
{
    FDate = _FDate;
}

예제에서 Car클래스는 DateTime형식의 값을 받는 두 번째 생성자를 추가하였습니다. 이에 따라 Car의 인스턴스를 생성할 때는 이전처럼 아무런 인수값이 없거나 필요한 인수값을 선택적으로 전달할 수 있습니다.

using mylibrary;

Car sedan = new(new DateTime(2021, 3, 4))
{
    Number = "1234"
};

Car truck = new()
{
    Number = "5678"
};

Console.WriteLine($"{sedan.Number}번 자동차의 출고일:{sedan.FDate}");
Console.WriteLine($"{truck.Number}번 자동차의 출고일:{truck.FDate}");
//1234번 자동차의 출고일:2021-03-04 오전 12:00:00
//5678번 자동차의 출고일:2022-04-03 오전 10:52:45
728x90