타입 이름을 두 번 쓰는 선언, 만들고 나서 한 줄씩 채우는 목록, 아무나 고칠 수 있는 필드. 이번 편에서는 var 와 초기화 문법으로 선언을 짧게 만들고, 프로퍼티로 값을 읽고 쓰는 규칙을 클래스 안에 가둡니다.
2024년 부산 콘텐츠 아카데미 퍼스널 트레이닝 수업에서 MyLesson Unity 실습 프로젝트의 Strategy_08_Var.cs · Strategy_13_ClassInitializer.cs · Strategy_12_Property.cs 로 가르친 내용입니다. 수업은 다섯 단계로 나뉘고, 단계마다 코드가 조금씩 바뀝니다.
var, 타입은 오른쪽을 보고 정한다
public void Run()
{
var i = 1;
var j = "j";
Program.WriteLine("i={0}, j={1}", i, j);
//Dictionary<long, List<int>> userInventory = new Dictionary<long, List<int>>();
var userInventory = new Dictionary<long, List<int>>();
var k = new GameObjectCreator();
var createdObj = k.Create<Image>("K Instance Object");
Program.WriteLine(createdObj);
createdObj.GetComponent<Image>().color = Color.blue;
// Error case 1
//var l;
//l = 5;
// Error case 2
//createdObj = 500;var 는 "타입을 오른쪽 값에서 정해 달라" 는 표시입니다. var i = 1 이면 int, var j = "j" 면 string 입니다. Dictionary<long, List<int>> 처럼 긴 타입을 두 번 쓰지 않아도 됩니다.
i=1, j=j
K Instance Object (UnityEngine.GameObject)var 로 되는 것
var i = 1;→ intvar k = new GameObjectCreator();var obj = k.Create<Image>(…);→ GameObject
var 로 안 되는 것 (주석의 Error case)
var l;처럼 값 없이 선언하기 (타입을 알 수 없음)createdObj = 500;처럼 나중에 다른 타입 넣기- var 는 동적 타입이 아니다. 컴파일할 때 타입이 정해진다
2편의 제네릭이 다시 나옵니다. where T : MonoBehaviour 조건으로, 빈 GameObject 를 만들고 T 컴포넌트를 붙여 돌려줍니다. Image 를 넘기면 화면에 파란 사각형이 생깁니다.
배열 · 리스트 · 사전 초기화
int[] array1_1 = new int[4] { 0, 1, 2, 3 };
int[] array1_2 = { 0, 1, 2, 3 }; // 암시적 축약
Program.WriteList(array1_2);
float[] array2_1 = new float[3] { 0.1f, 0.2f, 0.3f };
float[] array2_2 = new float[] { 0.1f, 0.2f, 0.3f }; // 암시적 축약
var array2_3 = new float[] { 0.1f, 0.2f, 0.3f }; // var 사용
Program.WriteList(array2_3);
string[,] array3_1 = new string[2, 3] { { "Heloon", "Leon", "Lucia" }, { "Darkan", "Karsian", null } };
string[,] array3_2 = new string[,] { { "Heloon", "Leon", "Lucia" }, { "Darkan", "Karsian", null } }; // 암시적 축약
var array3_3 = new string[,] { { "Heloon", "Leon", "Lucia" }, { "Darkan", "Karsian", null } }; // var 사용
Program.WriteList(array3_3);배열은 중괄호 안에 값을 늘어놓아 만들 수 있고, 크기와 타입은 값에서 알아냅니다. 2차원 배열 string[,] 도 같은 방식입니다.
List<string> myItems_1 = new List<string>();
List<string> myItems_2 = new List<string> { "ThunderBow", "ThunderSword", "ThunderShield", "FireArrow" }; // 원소 초기화
var myItems_3 = new List<string>() { "ThunderBow", "ThunderSword", "ThunderShield", "FireArrow" }; // var 사용
Program.WriteList(myItems_3);
Dictionary<string, int> weaponAttackPoints_1 = new Dictionary<string, int>();
string secondSword = "SecondSword";
int secondSwordAttackPoint = 20;
WeaponInfo thirdSword = new WeaponInfo { Name = "ThirdSword", AttackPoint = 30 };
var weaponAttackPoints_2 = new Dictionary<string, int> // 원소 초기화 및 var 사용
{
{ "FirstSword", 10 },
{ secondSword, secondSwordAttackPoint },
{ thirdSword.Name, thirdSword.AttackPoint }
};
Program.WriteList(weaponAttackPoints_2);List 와 Dictionary 도 만들면서 바로 채울 수 있습니다(컬렉션 초기화). 사전은 { 키, 값 } 묶음을 늘어놓습니다. 구조체 WeaponInfo 는 new WeaponInfo { Name = …, AttackPoint = … } 처럼 필드 이름을 적어 만들었습니다(객체 초기화).
ThunderBow
ThunderSword
ThunderShield
FireArrow
[FirstSword, 10]
[SecondSword, 20]
[ThirdSword, 30]| 모양 | 뜻 |
|---|---|
new int[] { 0, 1, 2, 3 } | 배열 초기화. 크기는 값의 개수 |
new List<string> { "A", "B" } | 컬렉션 초기화. 만든 뒤 Add 를 차례로 부른 것과 같다 |
new Dictionary<string, int> { { "A", 1 } } | 사전 초기화. Add(키, 값) 을 부른 것과 같다 |
new WeaponInfo { Name = "A" } | 객체 초기화. 만든 뒤 속성에 값을 넣은 것과 같다 |
객체 안의 목록까지 한 번에
public class Product
{
public string ProductId { get; set; }
private readonly List<SellItem> items = new List<SellItem>();
public List<SellItem> Items
{
get { return items; }
}Product.Items 는 get 만 있는 프로퍼티입니다. 다른 리스트로 바꿔 끼울 수는 없습니다. 그런데도 아래 코드는 동작합니다.
var order = new Shop
{
Products =
{
new Product
{
ProductId = "Package.Ruby.10000.FirstBonus.1000",
Items =
{
new SellItem { ItemId = "ruby", Quantity = 10000 },
new SellItem { ItemId = "free.ruby", Quantity = 1000 }
}
},
new Product
{
ProductId = "Package.Weapon.Archer",
Items =
{
new SellItem { ItemId = "bow.legendary", Quantity = 1 },
new SellItem { ItemId = "arrow.legendary", Quantity = 1000 }
}
}
}
};
Program.WriteLine(order);Items = { … } 는 Items 에 새 리스트를 넣는 것이 아니라, 이미 있는 Items 리스트에 Add 를 부르는 문법입니다. 그래서 setter 가 없어도 됩니다. 상점 · 상품 · 판매 품목처럼 겹겹이 들어 있는 데이터를 데이터 모양 그대로 적을 수 있습니다.
Package.Ruby.10000.FirstBonus.1000
ruby/10000
free.ruby/1000
Package.Weapon.Archer
bow.legendary/1
arrow.legendary/1000프로퍼티, 읽고 쓰는 규칙을 클래스 안에
public class Monster
{
private static int increment = 0;
private static int GetNextID()
{
increment++;
return increment;
}
public int Id { get; }
private string name;
public string Name { get => name; }
private int attackPoint;
public int AttackPoint
{
get
{
return attackPoint + buffAttackPoint;
}
}
private int healthPoint;
public int HealthPoint
{
get => healthPoint;
set
{
Program.WriteLine("---------- {0}'s new HP: {1}", name, value);
if (value > originHealthPoint)
value = originHealthPoint;
healthPoint = value;
}
}
private int originHealthPoint;
private int buffAttackPoint;
public Monster(string name, int attackPoint, int healthPoint)
{
this.Id = Monster.GetNextID();
this.name = name;
this.attackPoint = attackPoint;
this.healthPoint = healthPoint;
this.originHealthPoint = healthPoint;
}
public void AddBuffAttackPoint(int buff)
{
buffAttackPoint += buff;
Program.WriteLine("---------- Buff Attack +{0} to {1}", buff, name);
}
}| 멤버 | 모양 | 하는 일 |
|---|---|---|
Id | { get; } | 읽기 전용 자동 프로퍼티. 생성자에서만 값을 넣을 수 있다 |
Name | { get => name; } | 필드 name 을 읽기만 하게 내보낸다 |
AttackPoint | get 안에서 계산 | 기본 공격력 + 버프를 더해 돌려준다. 저장된 값이 아니라 계산된 값 |
HealthPoint | get + set | set 에서 최대 체력을 넘지 않게 자른다 |
public void Run()
{
Monster mon1 = new Monster("Goblin", 2, 2);
Monster mon2 = new Monster("Orc", 3, 1);
PrintMonster(mon1);
Program.WriteLine();
PrintMonster(mon2);
mon1.AddBuffAttackPoint(2);
mon2.HealthPoint = 10;
PrintMonster(mon1);
Program.WriteLine();
PrintMonster(mon2);
}ID: 1
Name: Goblin
AP: 2
HP: 2
ID: 2
Name: Orc
AP: 3
HP: 1
---------- Buff Attack +2 to Goblin
---------- Orc's new HP: 10
ID: 1
Name: Goblin
AP: 4
HP: 2
ID: 2
Name: Orc
AP: 3
HP: 1밖에서는 mon2.HealthPoint = 10 처럼 필드에 값을 넣듯 씁니다. 하지만 실제로는 set 이 불려 규칙이 적용되고, 오크의 체력은 최대치 1 로 잘립니다. 필드를 public 으로 열어 두면 이런 규칙을 지킬 곳이 없습니다.
set 의 로그는 값을 자르기 전에 찍힙니다. 그래서 "new HP: 10" 이라고 찍히지만 실제로 저장된 값은 1 입니다. 로그는 최종 값을 정한 뒤에 찍는 습관이 좋습니다.
돌아보며
이번 편에서 얻은 것
- var 는 컴파일할 때 타입이 정해지는 줄임말이다
- 컬렉션 · 객체 초기화로 데이터 모양 그대로 적을 수 있다
- 프로퍼티로 읽기 전용, 계산 값, 값 검사를 클래스 안에 둔다
아쉬운 점
- VAR 2단계의 2차원 배열은 마지막 칸이 null 이라, WriteList 가 null.ToString() 에서 예외를 낸다
- HealthPoint 의 로그가 값을 자르기 전에 찍힌다
정리
- var 는 오른쪽을 보고 타입을 정합니다. 값 없이 선언할 수 없고, 나중에 다른 타입을 넣을 수도 없습니다.
- 초기화 문법은 Add 와 속성 대입의 줄임말입니다. get 만 있는 목록 프로퍼티에도 { } 로 원소를 채울 수 있습니다.
- 프로퍼티는 규칙을 지키는 문입니다. 밖에서는 필드처럼 쓰고, 안에서는 계산과 검사를 합니다.
다음 편에서는 한 클래스를 여러 파일로 나누는 partial, 같은 이름의 타입을 구분하는 namespace, 그리고 에디터 메뉴를 만드는 MenuItem 을 봅니다.