상세 컨텐츠

본문 제목

[C#] 프로퍼티(Property)

.NET/C#

by 클리엘 클리엘 2021. 10. 7. 17:04

본문

728x90

1. 프로퍼티

 

C#에서 어떤 필드의 은닉성을 지키기 위해 필드에 값을 읽고 쓰는 메서드를 만들었다면

class Program
{
    static void Main(string[] args)
    {
        Car car = new Car();
        car.SetSpeed(100);
        car.GetSpeed();
    }
}

class Car
{
    private int speed;

    public int GetSpeed()
    {
        return speed;
    }

    public void SetSpeed(int i)
    {
        speed = i;
        WriteLine(speed);
    }
}

같은 기능을 프로퍼티로 완벽하게 대체할 수 있습니다.

class Program
{
    static void Main(string[] args)
    {
        Car car = new Car();
        car.Speed = 100;

        WriteLine(car.Speed);
    }
}

class Car
{
    private int speed;

    public int Speed
    {
        get {
            return speed;
        }

        set {
            speed = value;
        }
    }
}

프로퍼티는 메서드처럼 괄호를 가지지 않고 내부에 get/set 접근자를 가집니다. get은 값을 읽을 때, set은 값을 설정할 때 사용하는데 특히 set안에 value는 값을 설정할 때 해당 값을 읽어오는 키워드로 사용됩니다.

 

물론 값을 읽거나 쓰는 기능이 필요하지 않다면 그 에 맞춰 get이나 set을 생략해 줄 수 있습니다.

class Car
{
    private int speed;

    public int Speed
    {
        set {
            speed = value;
        }
    }
}

따라서 위 예제처럼 set만 있는 경우라면 값을 설정만 할뿐 읽어올 수는 없게 됩니다.

 

2. 자동 구현 프로퍼티

 

프로퍼티는 또한 위의 구현방식에서 더 간략한 방법인 '자동 구현 프로퍼티'를 사용할 수도 있습니다.

class Car
{
    public int Speed
    {
        get;
        set;
    }
}

극단적으로 간략화된 방법인데 소스코드상으로는 아무런 문제가 없습니다. 기존과 달리 speed필드가 사라졌으며 get에서 값을 반환하는 부분과 set에서 값을 설정하는 부분 모두 제거되었습니다. 이때 만약 필드에 초기값이 필요한 경우라면 프로퍼티 뒤에 해당 값을 부여해 줄 수 있습니다.

class Car
{
    public int Speed
    {
        get;
        set;
    } = 100;
}

사실 이 코드 자체가 특별히 뭔가를 수행하는 건 아니고 C# 컴파일러가 위 소스코드를 컴파일할 때 처음에 제시된 예제처럼 소스코드를 변환한 뒤 컴파일을 수행하는 방법으로 진행하게 되는 것입니다.

 

따라서 단순히 필드값을 읽고 쓰는 것이 전부라면 '자동 구현 프로퍼티'를 이용하는 편이 훨씬 소스코드 작성에 도움이 될 것입니다.

 

3. 초기화

 

클래스의 객체를 생성할때 특정 필드 값을 초기화하는 경우에도 프로퍼티를 사용할 수 있습니다.

class Program
{
    static void Main(string[] args)
    {
        Car car = new Car() { Speed = 100, Color = "Red" };
    }
}

class Car
{
    public int Speed
    {
        get;set;
    }

    public string Color
    {
        get;set;
    }
}

물론 이때도 초기화가 필요없는 필드는 무시할 수 있습니다.

class Program
{
    static void Main(string[] args)
    {
        Car car = new Car() { Color = "Red" };
    }
}

4. 초기화 전용(init only)

 

위 초기화 방식에서 프로퍼티의 접근자를 set에서 init로 바꿔주면

class Car
{
    public int Speed
    {
        get;init;
    }

    public string Color
    {
        get;init;
    }
}

객체 생성 시 초기화만 가능하고 이후에는 해당 프로퍼티를 통해 값을 설정할 수 없게 됩니다.

class Program
{
    static void Main(string[] args)
    {
        Car car = new Car() { Speed = 100, Color = "Red" };

        car.Speed = 100; //에러 초기화 이후 값을 변경할 수 없음.
    }
}

따라서 초기화에서만 값을 받고 더 이상 값의 변경이 필요 없는 경우에 init접근자를 사용할 수 있습니다.

 

4. 레코드(Record)

 

값형식의 객체와 참조 형식의 객체는 자신이 가진 값을 복사하거나 비교하는 과정에서 서로 다른 방식의 처리가 이루어집니다. 참조 형식에서의 데이터 복사는 얕은 복사지만 값 형식에서는 깊은 복사가 수행됩니다. 만약 참조 형식에서 깊은 복사를 수행하려면 직접 값이 복사되는 로직을 구현해야 합니다.

 

해당 객체에서 복사될 필드수가 많고 객체를 참조하는 대상이 많을 수록 실질적으로 값을 복사하는 값 형식은 일정 부분 성능에 부담을 가질 질 수밖에 없습니다. 하지만 참조 형식은 값이 복사되는 게 아니라 값을 참조하고 있는 주소만을 복사하기 때문에 상대적으로 값이 복사되는 동작의 부하가 적을 것입니다.

 

값의 비교 또한 값형식은 필드 하나하나를 1:1로 비교하지만 참조 형식은 값의 복사처럼 직접 비교에 필요한 부분을 구현해야 합니다.

 

레코드는 참조형식에서 오는 성능의 이점과 값 형식에서 오는 편리함의 이점만을 적용시킨 데이터 형식입니다. 레코드는 record 키워드를 통해 선언됩니다.

record Car
{
    public int Speed { get; set; }
    public string Color { get; set; }
}

레코드 형식을 복사하려면 값 형식처럼 서로 간에 대입 처리만 해주면 됩니다.

class Program
{
    static void Main(string[] args)
    {
        Car sedan = new Car() { Speed = 100, Color = "Red" };
        Car truck = sedan;

        WriteLine($"{truck.Speed}-{truck.Color}");
    }
}

물론 위와 같은 방법은 일반적인 참조형식의 얕은 복사와 다를게 없습니다. 레코드 형식은 내부적으로 복사를 위한 protected 생성자가 자동으로 만들어지는데 다음과 같이 with 식을 통해 생성자를 이용하면 깊은 복사가 수행됩니다. 더불어 특정 필드의 값만 바꿔서 복사를 진행할 수도 있습니다.

class Program
{
    static void Main(string[] args)
    {
        Car sedan = new Car() { Speed = 100, Color = "Red" };
        Car truck = sedan with { Speed = 80 }; //변경없이 그대로 복사하고 싶으면 {}만 사용

        WriteLine($"{truck.Speed}-{truck.Color}");
    }
}

복사뿐만 아니라 값을 비교하기 위한 Equal() 메서드도 자동으로 구현되는데 레코드는 원칙적으로 참조 형식이지만 Equal() 메서드를 따로 구현하지 않고도 그냥 사용할 수 있는 것입니다.

class Program
{
    static void Main(string[] args)
    {
        Car sedan = new Car() { Speed = 100, Color = "Red" };
        Car truck = sedan;

        WriteLine($"{sedan.Equals(truck)}"); //같은 값을 가지고 있으므로 true
    }
}

5. 무명형식

 

'무명'은 이름이 없는 것입니다. 따라서 무명 형식은 데이터는 있으나 이름이 없는 형태를 말하는데 특정 이름 없이 데이터만 가지고 개체를 생성할 수 있습니다.

class Program
{
    static void Main(string[] args)
    {
        var car = new { Speed = 100, Color = "Red" };

        WriteLine(car.Color); //Red
    }
}

new 다음에 이름이 없는 채로 임의의 필드를 생성해 값을 할당하고 개체를 생성하고 있습니다. 다만 무명 형식은 개체를 생성할 때 한번 값을 할당하면 해당 필드의 값은 바꿀 수 없습니다.

728x90

'.NET > C#' 카테고리의 다른 글

[C#] 일반화 프로그래밍  (0) 2021.10.14
[C#] 배열, 컬렉션, 인덱서  (0) 2021.10.13
[C#] 프로퍼티(Property)  (0) 2021.10.07
[C#] 인터페이스와 추상클래스  (0) 2021.10.07
[C#] 클래스(Class)  (0) 2021.10.06
[C#] 메서드  (0) 2021.09.27

관련글 더보기

댓글 영역