클래스를 작성하다 보면 필드를 public으로 선언해버리고 싶은 충동이 들때가 한두번이 아닙니다. 여차하면 의도하지 않게 데이터가 오염될 수 있다는 것을 잘 알면서도 괜찮을거야 하는 악마의 속삭임이 귓전에 맴돕니다. 무엇보다도 Get/Set 메소드 대신 할당연산자를 이용해서 필드를 읽거나 할당하고 싶은 생각이 들기 시작하면 이 충동은 더욱 커집니다.따라서 은닉성을 지키면서 편의성 좋게 코드를 작성하기위해 사용하는 것이 프로퍼티입니다.
우선 우리가 알고있는 Get/Set을 이용한 코드를 알아보도록 하겠습니다
Class MyClass
{
private int myField;
public int GetMyField(){ return myField; }
public void SetMyField(int NewValue){ myField = NewValue; }
}
//그리고 이렇게 사용될겁니다.
public Class MainApp : MonoBehaviour
{
Start()
{
MyClass obj = new MyClass();
Debug.Log(obj.GetMyField());
obj.SetMyField( 3 );
}
}
서두가 길었는데 이제부터 프로퍼티를 사용하면 더욱 우아하게 코드를 작성 할 수 있습니다.
class 클래스이름
{
데이터형식 필드_이름;
접근한정자 데이터형식 프로퍼티_이름
{
get
{
return 필드_이름;
}
set
{
필드_이름 = value;
}
}
}
프로퍼티 선언 문법에서 get{}과 set{}을 일컬어 접근자라고 합니다.
get접근자는 필드로 부터 값을 읽어오고
set접근자는 필드에 값을 할당합니다. set접근자 안에있는 value키워드를 주목하기 바랍니다. 이친구는 누구도 선언한 적이 없지만, C# 컴파일러는 set접근자의 암묵적 매개변수로 간주하므로 전혀 문제 삼지 않습니다.
using System;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
namespace Property
{
class BirthdayInfo
{
private string name;
private DateTime birthday;
//필드 name의 프로퍼티 구현
public string Name // 접근한정자 데이터_형식 프로퍼티_이름
{
get
{
return name;
}
set
{
name = value;
}
}
public DateTime Birthday
{
get
{
return birthday;
}
set
{
birthday = value;
}
}
//이렇게 get접근자만 있으면 읽기 전용 프로퍼티가 됩니다.
public int Age
{
get
{
return new DateTime(DateTime.Now.Subtract(birthday).Ticks).Year;
}
}
}
public class 프로퍼티 : MonoBehaviour
{
void Start()
{
//원래는 Get/Set함수를 써야만 했지만
//이제 프로퍼티를 통해 =연산자로 필드에 값을 대입하고 불러올수있습니다.
BirthdayInfo birth = new BirthdayInfo();
birth.Name = "서현"; //이전 코드 : birth.SetName("서현")
birth.Birthday = new DateTime(1991, 6, 28); //이전 코드 : birth.SetBirthday(new DateTime(1991, 6, 28));
Debug.Log($"Name : {birth.Name}");
Debug.Log($"Birthday : {birth.Birthday.ToShortDateString()}");
Debug.Log($"Age : {birth.Age}");
}
}
}
📢 C# 7.0에서 도입된 형식입니다.
프로퍼티를 사용하여 꽤 많은 부분이 간편하게 변했지만 많은 경우에 중복된 코드를 작성하고 있다는 생각이듭니다. 다음 코드는 자동 구현 프로퍼티를 통해 더욱 간편해진 프로퍼티를 작성했습니다.
using System;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
namespace AutoImplementedProperty
{
class BirthdayInfo
{
//프로퍼티 필드를 선언할 필요없이 그저 get,set뒤에 ;만 붙여주면 됩니다.
//선언과 동시에 초기화를 수행할 수 있습니다.
public string Name { get; set; } = "Unknown";
public DateTime Birthday { get; set; } = new DateTime(1, 1, 1);
//get함수만 구현하여 읽기전용 필드로 사용할 수 있습니다.
public int Age
{
get
{
return new DateTime(DateTime.Now.Subtract(Birthday).Ticks).Year;
}
}
}
public class 자동구현프로퍼티 : MonoBehaviour
{
void Start()
{
BirthdayInfo birth = new BirthdayInfo();
Debug.Log($"Name : {birth.Name}"); //초깃값으로 설정된 값이 출력됩니다.
Debug.Log($"Birthday : {birth.Birthday.ToShortDateString()}");
Debug.Log($"Age : {birth.Age}");
birth.Name = "서현";
birth.Birthday = new DateTime(1991, 6, 28);
Debug.Log($"Name : {birth.Name}");
Debug.Log($"Birthday : {birth.Birthday.ToShortDateString()}");
Debug.Log($"Age : {birth.Age}");
}
}
}
자동 구현 프로퍼티가 편하다는 것은 이제 잘 알게 되었습니다.
그런데 C#컴파일러는 뒤에서 어떤 작업을 해주는 걸까요?
또한 자동구현 프로퍼티를 선언할때 public으로 선언했는데 과연 필드는 제대로 private로 적용될까요?

직접 선언한 Property.BirthdayInfo 클래스 안에 마름모 아이콘의 birthday와 name필드가 있습니다.이들은 우리가 직접 선언한 인스턴스입니다.
이번에는 AutoImplementedProperty 예제 프로그램에 AutoImplementedProperty.Birthdayinfo 안쪽을 보면 여기에도 마름모 모양 아이콘의 필드가 두개 보입니다. < Birthday >k_BackingField는 Birthday 프로퍼티를 위해, 컴파일러가 물밑에서 선언해준 것입니다. 덕분에 우리는 한결 편한 프로그래밍을 할 수있습니다.
또한 < Birthday >k_BackingField 필드가 제대로 private로 선언 되어있는것이 보입니다.
이전 장에서 객체를 생성할 때 매개변수를 입력받아 객체의 각필드를 초기화하는 방법에 대해 이야기 했습니다. 이번에는 객체를 생성할 때 각필드를 초기화 하는 또 다른 방법을 소개하려 합니다.
바로 프로퍼티를 이용한 초기화 입니다.
클래스_이름 인스턴스 = new 클래스_이름()
{
프로퍼티1 = 값, //세미콜론(;)이 아니라 콤마(,)입니다.
프로퍼티2 = 값,
프로퍼티3 = 값
}; //(;)세미콜론으로 마무리 해야함
이와 같이 객체를 생성할 때 <프로퍼티 = 값> 목록에 객체의 모든 프로퍼티가 올 필요는 없습니다.
초기화 하고 싶은 프로퍼티만 넣어서 초기화하면 됩니다.
매개변수가 있는 생성자를 작성할 때와 달리 어떤 필드를 생성자 안에서 초기화 할지 미리 고민할 필요가 없습니다.
앞에서 만들었던 BirthdayInfo클래스를 예로 들어 프로퍼티를 이용한 객체를 생성하고 초기화 해보겠습니다.
BirthdayInfo birth = new BirthdayInfo()
{
Name = "서현", //세미콜론(;)이 아니라 콤마(,)입니다.
Birthday = new DateTime(1991, 6, 28)
}; //(;)세미콜론으로 마무리 해야함
생성자에 매개변수를 넣지않고 인스턴스를 만든 다음 필드를 초기화할수 있는 장점이있습니다.
using System;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
namespace ConstructorWithProperty
{
class BirthdayInfo
{
public string Name { get; set; } //프로퍼티로 필드를 만든다.
public DateTime Birthday { get; set; }
//생년월일을 넣으면 현재 나이를 알려줄 수 있도록 한다.
public int Age { get { return new DateTime(DateTime.Now.Subtract(Birthday).Ticks).Year; } }
}
public class 프로퍼티로_초기화 : MonoBehaviour
{
void Start()
{
//프로퍼티를 이용한 초기화
BirthdayInfo birth = new BirthdayInfo()
{
Name = "서현",
Birthday = new DateTime(1991, 6, 28)
};
Debug.Log($"Name : {birth.Name}");
Debug.Log($"Birth : {birth.Birthday.ToShortDateString()}");
Debug.Log($"Age : {birth.Age}");
}
}
}
📢 유니티에서는 지원 하지 않는 스크립트 입니다
(2021.3 이후 버전에서는 C# 9.0을 지원하지만 해당 스크립트 기능은 지원하지 않음)
참고) 유니티가 C# 9.0에서 지원하지 않는 기능
의도치 않게 데이터가 오염되는 일이 종종 있는데 C#에는 데이터의 오염을 방지 할 수 있는 장치가 여럿 있습니다. 접근한정자, readonly 필드, readonly 구조체, 튜플 등이 그 예죠. 하지만 프로퍼티를 읽기 전용으로 선언하는 방법이 조금 불편했는데요.
다음과 같이 생성자를 통해 필드를 초기화 하고 그 필드에 접근하는 프로퍼티는 get접근자만 갖도록 해야했습니다.
class Transaction
{
//생성자를 이용한 초기화
public Transaction(string _from,string _to, int _amount)
{
from = _from; to =_to, amount = _amount;
}
string from;
string to;
int amount;
//필드에 접근하는 프로퍼티는 get만 갖도록 해야함
public string From { get{return from;} }
public string To { get{return to;} }
public int Amount { get{return amount;} }
}
C# 9.0에 이르러서는 읽기전용 프로퍼티를 아주 간편하게 선언 할 수 있도록 개선하였ㅅ브니다. 접근자가 새로 도입 되었거든요.
init접근자는 set접근자 처럼 외부에서 프로퍼티를 변경할 수 있지만, 객체 초기화를 할 때만 프로퍼티 변경이 가능하다는 점이 다릅니다.
init을 사용하는 방법은 다음과 같습니다. 자동구현 프로퍼티를 선언하면서 set접근자 대신 init 됩니다.
이렇게 선언한 프로퍼티를 "초기화 전용 자동 구현(초기화 전용 Setter)" 프로퍼티라고 합니다.
public string From {get; init;}
public string To {get; init;}
public int Amount {get; init;}
예제 프로그램을 만들어서 테스트 해보겠습니다.
초기화가 한차례 이루어 진 후 변경되면 안되는 데이터는 어떤 것이 있을까요? 성적표, 번죄 기록, 각종 국가 기록,금융 거래 기록 등등 이루 셀 수 없습니다. 우리는 그중에서 돈 거래를 표현하는 Trasaction 클래스를 예제 코드에 선언 하고, 이 클래스의 프로퍼티(From은 주는 사람,To는 받는 사람,Amount는 거래량을 의미)를 초기화 전용으로 만들어 보겠습니다.
namespace InitOnly
{
class Transaction
{
public string From { get; init; }
public string To { get; init; }
public int Amount { get;init; }]
public ovveride string ToString()
{
return $"{From,-10} -> {To,-10} : ${Amount}";
}
}
class MainApp
{
static void Main(string[] args)
{
//한번 초기화하면 변경할 수 없습니다.
Transaction tr1 = new Transaction{From="Alice", To="Bob", Amout=100};
Transaction tr2 = new Transaction{From="Bob", To="Charlie", Amout=50};
Transaction tr3 = new Transaction{From="Charlie", To="Alice", Amout=50};
Console.WriteLine(tr1);
Console.WriteLine(tr2);
Console.WriteLine(tr3);
}
}
}
객체 초기화가 이루어진 후에 초기화 전용 자동 구현 프로퍼티를 수정하려 들면 init접근자는 초기화 이후에 발생하는 프로퍼티 수정을 허용하지 않으므로, C#컴파일러는 에러 메시지를 출력합니다.
📢 C# 9.0에서 도입된 형식입니다.
불변(immutable) 객체는 내부 상태(데이터)를 변경 할 수 없는 객체를 말하는데요.
상태를 변경 할 수 없다는 특성 때문에 불변 객체는 데이터 복사와 비교가 많이 이뤄집니다. 새로운 상태를 표현하려고 기존 상태를 복사한 뒤 이 중 일부를 수정해서 새로운 객체를 만들고, 상태를 확인하기 위해 객체내용을 자주 비교합니다. 레코드(record)는 불변 객체에서 빈번하게 이뤄지는 이 두 가지 연산을 편리하게 수행할 수 있도록 도입된 형식입니다.
레코드에 들어가기 전에 참조 형식과 값 형식에 대해서 잠깐 언급하고 가보겠습니다.
참조 형식은 클래스의 모든 필드를 readonly로 선언하면 불변 객체를 만들수 있습니다.
값 형식은 readonly struct로 구조체를 선언하면 됩니다. 컴파일러가 모든 필드를 readonly로 선언하도록 강제하니까요.
값 형식 객체는 다른 객체에 할당할 때 깊은 복사를 수행합니다. 깊은 복사란 모든 필드를 새 객체가 가진 필드에 1:1로 복사하는것을 말합니다. 배열의 요소에 입력하거나 함수 인수로 사용할 때도 늘 깊은 복사를 합니다.
값 형식은 필드가 많으면 많을수록 복사 비용이 커지게 됩니다.
왜냐하면 깊은 복사로 새 객체가 기존에 가진 모든 필드를 1:1 복사하기 때문이죠.
객체를 여러 곳에서 사용해야 하는 경우에는 더 커지죠.
⭐ 참조형식은 이런 오버헤드가 없습니다. 객체가 참조하는 메모리 주소만 복사하면 되니까요.
물론 단점이 없는것은 아닙니다. 참조 형식은 프로그래머가 직접 깊은 복사를 구현해야 합니다.
⭐ 값 형식은 객체를 비교할 때 기본적으로 내용을 비교하는데 모든 필드를 1:1로 비교합니다. 불변 객체에 필요한 비교 방법이죠.
참조 형식은 어떨까요? 참조형식끼리 내용 비교를 할 수 있으려면 프로그래머가 직접 비교 코드를 작성해야 합니다. 보통은 object로부터 상속하는 Equals()메소드를 직접 오버라이딩합니다.
불변 객체를 참조 형식으로 선언하면 함수 호출인수나 컬렉션 요소로 사용할 때 복사 비용을 줄일 수 있습니다. 한편 불변 객체는 새 상태 표현과 상태 확인을 위해 깊은 복사와 내용 비교가 필수적이므로 값 형식으로 선언하는 편이 프로그래머에게 편리한 부분이 많을터이고요.
🤔불변 참조 형식의 비용 효율과 불변 값 형식의 편리함을 모두 얻을 수 있는 방법이 있다면 얼마나 좋을까요?
✍정리하겠습니다.
레코드 형식은 (값 형식처럼 다룰 수 있는) 불변 참조 형식으로, 참조 형식의 비용 효율과 값 형식이 주는 편리함을 모두 제공합니다.
//유니티에서 사용가능
namespace Record
{
record Person
{
public string Name { get; }
public int Age { get; }
public Person(string name, int age)
=> (Name, Age) = (name, age);
}
public class 레코드 : MonoBehaviour
{
void Start()
{
Person p = new Person("Tom", 30);
string s = p.Name;
}
}
}
init 키워드는 해당 속성을 Immutable(불변) 속성으로 만드는 역활을 하게 됩니다.
init이 하나의 속성을 Immutable로 만드는 것이라면, record 타입은 객체 전체를 Immutable로 만드는 것으로 볼 수 있습니다.
record 타입을 정의할 때 위와 같이 생성자를 쓰는 대신, 아래와 같이 init과 객체 초기자(Object Initializer)를 사용할 수 있는데 만약 필드/속성의 수가 많다면, 아래 표현이 더 간결할 수 있습니다.
//유니티에서 (초기화 전용 세터) 사용 불가능
namespace Record
{
record Person
{
public string Name { get; init; } //유니티에선 사용 불가능
public int Age { get; init; } //유니티에선 사용 불가능
}
public class 레코드 : MonoBehaviour
{
void Start()
{
Person p = new Person
{
Name = "Tom",
Age = 30
};
}
}
}
기존 프로퍼티 사용 방법과 동일하지만 record 키워드를 사용합니다.
이렇게 선언한 레코드로 인스턴스(객체)를 만들면, 불변 객체가 만들어집니다.
레코드는 어디서에 빛을 발휘할까요?
복사할 때, 비교할 때 유용합니다.
record 타입은 객체 전체가 불변 참조 타입인데, 만약 객체 중 일부만 변경하여 새로운 record 객체를 만들고 싶다면, C# 9.0에서 새로 도입된 with 키워드를 사용할 수 있습니다. 예를 들어, 아래 예제에서 Person 타입의 객체로 tom1을 만들었을 때, tom1 객체 중 나이만 변경하고 나머지 데이터는 동일하게 tom2 객체를 만들고 싶다면, 아래와 같이 with 키워드를 사용할 수 있습니다.
(유니티 2021.3버전 에서는 set접근자를 명시해야만 쓸 수 있었습니다.)
namespace Record
{
record Person
{
public string Name { get; }
public int Age { get; set; } //with를 이용한 레코드 복사를 위해 set접근자 사용
public Person(string name, int age)
=> (Name, Age) = (name, age);
}
public class 레코드 : MonoBehaviour
{
void Start()
{
Person tom1 = new Person("Tom", 30);
//with로 나이만 40으로 변경했다.
//유니티에서는 set 접근자를 명시해야만 사용할 수 있다.(불변이 아니게 됨)
Person tom2 = tom1 with { Age = 40 };
//그대로 복사했을때
Person tom3 = tom1;
//Equals로 비교하기
Debug.Log(tom1.Equals(tom2)); //false
Debug.Log(tom1.Equals(tom3)); //true
//ReferenceEquals로 비교하기
Debug.Log(ReferenceEquals(tom1, tom2)); //false
Debug.Log(ReferenceEquals(tom1, tom3)); //true
}
}
}
record 타입은 불변 참조 타입으로 두 record 객체를 비교할 때, 객체 내의 값들이 동일한 지를 비교하는 경우가 더 많을 것입니다.
이러한 맥락에서 record 타입의 Equals() 메서드는 record 객체의 데이터 값들이 동일한 지를 체크하고 데이터가 동일하면 참을 리턴합니다.
아래 예제에서 두 객체의 레퍼런스는 다를 것이므로 ReferenceEquals()은 false를 리턴하고, 두 객체의 데이터가 동일하므로 Equals()은 true를 리턴합니다.
namespace Record
{
record Person
{
public string Name { get; }
public int Age { get; }
public Person(string name, int age)
=> (Name, Age) = (name, age);
}
public class 레코드 : MonoBehaviour
{
void Start()
{
Person p1 = new Person("Tom", 30);
Person p2 = new Person("Tom", 30);
Debug.Log(p1.Equals(p2)); //true
Debug.Log(ReferenceEquals(p1, p2)); //false
}
}
}
컴파일러는 레코드의 상태를 비교하는 Equals()메소드를 자동으로 구현합니다.
클래스를 비교하기 위해선 Equals()메소드를 오버라이딩하여야 하고,
레코드는 참조 형식이지만 값형식처럼 Equals()메소드를 구현하지 않아도 비교가 가능합니다.
namespace Record
{
record Person
{
public string Name { get; }
public int Age { get; }
public Person(string name, int age)
=> (Name, Age) = (name, age);
}
class ClassPerson
{
public string Name { get; }
public int Age { get; }
public ClassPerson(string name, int age)
=> (Name, Age) = (name, age);
//object클래스의 Equls를 재정의 합니다.
public override bool Equals(object obj)
{
ClassPerson target = (ClassPerson)obj;
if (this.Name == target.Name && this.Age == target.Age)
return true;
else return false;
}
}
public class 레코드 : MonoBehaviour
{
void Start()
{
ClassPerson cp = new ClassPerson("Tom", 30); //클래스
ClassPerson cp2 = new ClassPerson("Tom", 30);
Person p1 = new Person("Tom", 30); //레코드
Person p2 = new Person("Tom", 30);
//Equals를 명시적으로 구현하는 ClassPerson의 인스턴스 두개가 같은 상태를 갖고 있을 때
//이둘을 비교하면 참(True)이 반환 됩니다.
Debug.Log(cp.Equals(cp2)); //true
Debug.Log(ReferenceEquals(cp, cp2)); //false
Debug.Log("-----------↑ 클래스, ↓ 레코드-----------");
//비슷한 경우, Equals를 구현하지 않았지만 같은 상태를 지닌 인스턴스 둘을 비교하면 마찬가지로 참(True)이 반환됩니다.
Debug.Log(p1.Equals(p2)); //true
Debug.Log(ReferenceEquals(p1, p2)); //false
}
}
}
C#에는 여러 형식이 있습니다. int,double,string,FileStream,MyClass 등 말입니다.
이번 절에는 이름이 없는 형식, 무명 형식(Anonymous Type) 을 설명하려 합니다.
형식은 왜 고려해야할까요? 형식을 이용해서 인스턴스를 만들기 때문입니다.
int a;
double b;
무명형식은 형식의 선언과 동시에 인스턴스를 할당 합니다.
이 때문에 인스턴스를 만들고 다시는 사용하지 않을 때 무명형식이 요긴합니다.
무명 형식의 선언 예는 다음과 같습니다.
//괄호{} 사이에 임의의 프로퍼티 이름을 적고 값을 할당하면 그대로 새 형식의 프로퍼티가 됩니다.
var myInstance = new { Name = "배건하", Age = "27" };
//무명 형식의 인스턴스는 여느 객체처럼 프로퍼티에 접근할 수 있습니다.
Debug.Log(myInstance.Name, myInstance.Age);
무명 형식에서 주의할 점이 있는데, 그것은 무명형식의 프로퍼티에 할당된 값은 변경불가능하다는 사실입니다.
한마디로 무명 형식의 인스턴스가 만들어지고 난 다음에는 읽기만 할 수 있다는 이야기입니다.
지금 이러한 특징들을 보면 무명 형식이 무용지물 인것 같지만, 15장에서 설명할 LINQ와 함께 사용한다면 아주 요긴하다는 사실을 깨닫게 됩니다.
무명형식의 예제프로그램을 하나 만들어 보겠습니다.
namespace AnonymousType
{
public class 무명형식 : MonoBehaviour
{
void Start()
{
var a = new { Name = "배건하", Age = 28 };
Debug.Log(a.Name);
var b = new { Subect = "수학", Scores = new int[] {90,80} };
Debug.Log(b.Subect);
foreach (var c in b.Scores)
Debug.Log(c);
}
}
}
인터페이스는 메소드 뿐만아니라 프로퍼티와 인덱서도 가질 수 있습니다.
프로퍼티나 인덱서를 가진 인터페이스를 상속하는 클래스가 "반드시" 해당 프로퍼티와 인덱서를 구현해야 하는것은 물론입니다.
역시 당연한 이야기지만 인터페이스에 들어가는 프로퍼티는 구현을 갖지않습니다.
여기에 한가지 문제가 있는데, 인터페이스의 프로퍼티 선언은 클래스의 자동 구현 프로퍼티 선언과 그 모습이 동일하다는 사실입니다.
다음은 프로퍼티 선언 형식입니다.
interface 인터페이스_이름
{
public 형식 프로퍼티_이름1 { get; set; }
public 형식 프로퍼티_이름2 { get; set; }
}
다음은 프로퍼티를 가진 인터페이스와 이를 상속하는 파생클래스에
get과 set을 직접 명시하는 예제입니다.
interface IProduct
{
public string ProductName { get; set; }
}
class Product : IProduct
{
private string productName;
//get,set을 직접 명시했습니다.
public string ProductName
{
get{ return productName; }
set{ productName = value; }
}
}
↓ 자동구현 프로퍼티로 구현하는 예제입니다.
interface IProduct
{
public string ProductName { get; set; }
}
class Product : IProduct
{
//자동 구현 프로퍼티 사용했을때입니다.
public string ProductName { get; set; }
}
인터페이스 이야기를 했으니 추상 클래스 이야기를 안할 수 없습니다. 추상클래스는 클래스처럼 구현된 프로퍼티를 가질수 있는 한편, 인터페이스처럼 구현되지 않은 프로퍼티도 가질수 있습니다.
추상 클래스에서는 이것을 추상 프로퍼티(Abstract Property)라고 합니다.
추상 메소드가 그랬던 것처럼, 추상 프로퍼티 역시 인터페이스의 프로퍼티와 다를 것이 없습니다. 파생 클래스가 해당 프로퍼티를 구현하도록 강제하는 것일 뿐입니다.
그럼 추상 클래스의 추상 프로퍼티는 어떻게 선언할까요? 인터페이스 처럼 구현을 비워 놓은 것만으로는 추상 프로퍼티를 만들 수 없습니다. C# 컴파일러가 자동 구현 프로퍼티로 간주하고 구현을 자동으로 채워 넣을 테니까요. 그래서 추상 프로퍼티는 다음과 같이 abstract 한정자를 이용해서 선언합니다.
abstract class 추상_클래스_이름
{
abstract 데이터_형식 프로퍼티_이름 { get; set; }
}
다음은 추상 프로퍼티를 갖는 추상 클래스와 이를 상속하는 파생 클래스의 예제 코드입니다.
abstract class Product
{
private static int serial = 0;
public string SerialID //추상클래스는 구현을 가진 프로퍼티와
{
get { return String.Format("{0:d5}", serial++); }
}
abstract public DateTime ProductDate { get; set; } //구현이 없는 추상 프로퍼티 모두를 가질 수 있습니다.
}
class MyProduct : Product
{
public override DateTime ProductDate { get; set; } //파생 클래스는 기반 클래스의 모든 추상 메소드뿐 아니라
//추상 프로퍼티를 재정의(override)해야 합니다.
}
추상 프로퍼티 예제 프로그램 입니다.
namespace PropertiesInAbstractClass
{
abstract class Product
{
private static int serial = 0;
public string SerialID
{
get { return string.Format("{0:d5}", serial++); }
}
abstract public DateTime ProductDate { get; set; }
}
class MyProduct : Product
{
public override DateTime ProductDate { get; set; }
}
public class 추상클래스의_프로퍼티 : MonoBehaviour
{
void Start()
{
Product product_1 = new MyProduct()
{ ProductDate = new DateTime(2022, 01, 10) };
Debug.Log($"Product: {product_1.SerialID}, ProductDate: {product_1.ProductDate}");
}
}
}
출력
Product: 00000, ProductDate: 2022-01-10 오전 12:00:00