Chapte09. 프로퍼티
1. 프로퍼티
> 프로퍼티란 기존 클래스에서 은닉된 멤버들에 대한 접근을 허용하는 메서드를(Get/Set) 보다 편리하게 사용할 수 있도록 만들어주는 도구임
1) 기본문법
class 클래스{
데이터형 필드;
접근한정자 데이터형 프로퍼티{
get{
return 필드;
}
set{
필드 = value; // value는 프로퍼티 set 접근자의 매개변수로 간주됨
}
}
}
class MyClass{
private int myField;
public int MyField{
get{
return myField;
}
set{
myField = value;
}
}
}
1.1) 사용법
Myclass obj = new MyClass();
obj.MyField = 3; // set
Console.WriteLine(obj.MyField); // get
+) set을 생략하고, get만 정의하면 읽기 전용 프로퍼티가 됨.
2) 자동 구현 프로퍼티
class MyClass{
private string name;
private string phoneNumber;
public string Name{
get{
return name;
}
set{
name = value;
}
}
public string phoneNumber{
get{
return phoneNumber;
}
set{
phoneNumber = value;
}
}
}
▲ 위의 프로퍼티를 보면 get은 단순히 필드를 반환하고, set은 받은 값을 필드에 적용시키는 역할만 한다. 이 경우 더 간소화 시키기 위해 자동구현 프로퍼티를 사용함
class MyClass{
public string Name{get; set;}
public string PhoneNumber{get; set;}
} // 자동구현
class MyClass{
public string Name{get; set;} = "Unknown"
public string PhoneNumber{get; set;} = "000-0000"
} // 자동구현과 동시에 초기화
MyClass obj = new MyClass();
obj.Name = "이명진"
obj.PhoneNumber = "123-4567"
> 매우 간단해진 걸 볼 수 있다.
3) 프로퍼티와 생성자
> 객체를 생성할 때 프로퍼티를 이용하여 초기화할 수 있다.
> 위의 자동구현과 동시에 초기화와의 차이점을 보면 기초 값을 프로퍼티 작성과 동시에 넣어주는 것이라 이는 프로퍼티를 생성과 동시에 초기화하는 것과 명백히 다르다.
클래스 객체 = new 클래스()
{
프로퍼티1 = 값,
프로퍼티2 = 값,
프로퍼티3 = 값
};
MyClass obj = new MyClass();
{
Name = "이명진",
PhoneNumber = "000-0000"
};
4) 초기화 전용 자동 구현 프로퍼티(set -> init)
> get만 선언하는 읽기 전용 프로퍼티를 생성 당시에만 초기화할 수 있도록 하고, 이후에는 변경할 수 없도록 조절하는 프로퍼티다
> 구체적인 예로는 생년월일이 있다. 이유는 생년월일은 값을 필요로 하지만 구조적으로 변경될 수 없기 때문임(태어난 날을 바꿀 수는 없으므로).
class MyClass{
public string Name{get; set;} // 기존 자동구현 프로퍼티
public DateTime Birthday{get; init;} // 초기화 및 읽기 전용 자동구현 프로퍼티
}
MyClass obj = new MyClass();
{
Name = "이명진"
Birthday = new DateTime(1998, 8, 6)
};
> 프로퍼티를 이용한 생성자 초기화
> 이후 obj.Name을 통해 값을 변경할 때는 아무 문제가 없지만, obj.Birthday를 통해 값을 변경할 때는 컴파일 에러가 발생
> 이유는 Birthday 프로퍼티는 init 키워드를 통해 생성 당시에만 값을 초기화할 수 있고, 이후는 불가능하기 때문이다.
5) 초기화 강제(required)
> required 키워드는 초기화가 반드시 필요한 프로퍼티를 초기화할 수 있도록 강제한다
> 초기화를 하지 않으면 컴파일 에러를 발생시킨다.
class MyClass{
public required string Name{get; set;}
public required DateTime Birthday{get; init;}
}
6) 불변 객체(record)
> 불변 객체란 내부 필드를 변경할 수 없는 객체를 말함
> 불변 객체는 보통 불변 객체 일부를 수정한 새로운 객체를 만들어 불변 객체와 비교를 하기위해 사용됨
record MyClass{
public string Name {get; set;}
public DateTime Birthday {get; init;}
}
MyClass obj = new MyClass()
{
Name = "이명진",
Birthday = new DateTime(1998, 8, 6)
};
> 불변 객체 생성
> 보다시피 기존 클래스를 통한 객체 생성과정과 똑같다
> 하지만 내부 값은 더 이상 변경할 수 없음
6.1) 레코드 복사(with)
> with 키워드는 레코드 형식을 위한 복사 키워드임
MyClass obj = new MyClass() {Name = "이명진", Birthday = new DateTime(1998, 8, 6)};
MyClass obj2 = obj with {Birthday = new DateTime(1998, 9, 7)};
▲ with는 obj를 그대로 복사하되, Birthday 부분만 위처럼 변경하여 복사함.
6.2) 레코드 객체 비교(Equals())
> Eqauls 메서드는 레코드의 내용을 비교한다.
MyClass obj = new MyClass() {Name = "이명진", Birthday = new DateTime(1998, 8, 6)};
MyClass obj2 = new MyClass() {Name = "이명진", Birthday = new DateTime(1998, 8, 6)};
Console.WriteLine(obj.Equals(obj2)); // True
※ 주의
record가 아닌 class로 정의한 Equals 메서드는 참조 동일성을 비교한다.
class MyClass{
public required string Name{get; set;}
public required DateTime Birthday{get; init;}
}
MyClass obj = new MyClass() {Name = "이명진", Birthday = new DateTime(1998, 8, 6)};
MyClass obj2 = new MyClass() {Name = "이명진", Birthday = new DateTime(1998, 8, 6)};
Console.WriteLine(obj.Equals(obj2)); // False
> 내용은 같지만, obj가 참조하고 있는 곳과 obj2가 참조하고 있는 곳은 명백히 다르기 때문임.
2. 인터페이스 / 추상클래스의 프로퍼티
1) 인터페이스
> 인터페이스도 프로퍼티를 가질 수 있다
> 하지만 프로퍼티의 구현은 가질 수 없다
> 상속하는 파생 클래스는 프로퍼티의 구현을 강제받는다
interface IBirthday
{
public string Name{get; set;}
public DateTime Birthday{get; init;}
}
class MyClass : IBirthday
{
private string name;
public string Name{ // 직접구현
get{return name;}
set{name = value;}
}
public DateTime Birthday{get; init;} // 자동구현
}
▲ 위 처럼 인터페이스는 프로퍼티를 정의만할 뿐 구현할 수 없어 파생클래스스에서 프로퍼티를 구현하는데, 이 경우 직접구현 뿐만 아니라 자동구현도 할 수 있다.
2) 추상 클래스
> 추상 클래스는 인터페이스처럼 프로퍼티를 가지고, 파생 클래스들에게 강제한다.
> 다만 구현부도 가질 수 있다는 점에서 차이가 있다.(=>abstract)
abstract class Birthday{
public string Name{get; set;} // 구현을 가진 프로퍼티(자동구현)
abstract public DateTime Birthday{get; init;} // 구현이 없는 프로퍼티
}
class MyClass : ABirthday{
public override DateTime Birthday(get; init;) // 추상 멤버 뿐 아니라 추상 프로퍼티 또한 override, 자동구현 사용
}
> 이전 추상 클래스를 상속했을 때 구현부는 무조건 override를 했던 것처럼 프로퍼티 또한 override 하여 재정의 해줘야 한다.
> 이미 추상 클래스에서 구현부를 가진 프로퍼티는 바로 사용할 수 있고, 구현부가 없는 프로퍼티는 따로 재정의해줘야 사용할 수 있다.
Chapter10. 배열과 컬렉션 그리고 인덱서
1. ^
> ^는 배열을 사용할 때 마지막 인덱스부터 사용할 수 있도록 해주는 키워드
1) 기본문법
^1
^2
^3
...
> ^1은 배열의 제일 마지막 요소를 나타냄
> 예를 들어 int[] num = new int[5] 라고 가정했을 때 ^1은 num[4]를 나타낸다(마지막 요소)
2) 사용법
System.Index last = ^1; // System.Index 형의 객체에 담는 경우
num[last] = 10; // num[4] = 10; 과 같다
num[^1] = 10; // 바로 쓰는 경우, num[4] = 10; 과 같다
2. 배열 초기화 방법
string[] arr = new string[2] {"안녕", "Hello"};
string[] arr2 = new string[] {"안녕", "Hello"};
string[] arr3 = {"안녕", "Hello"};
3. System.Array
> 배열들의 클래스
> int[] 도 정확히 말하면 System.Int32[]이고, System.Array를 상속한 클래스임
int[] nums = new int[]{1, 5, 4, 2, 3};
1) 정적(static) 메서드
- Sort() : 정렬
Array.Sort(nums); // 1, 2, 3, 4, 5
- Clear() : 요소 초기화
Array.Clear(nums, 0, nums.Length); // 0, 0, 0, 0, 0
- IndexOf() : 인덱스 반환
Array.IndexOf(nums, 4); // 2
- BinarySearch<T>() : 인덱스 반환(이진탐색)
Array.BinarySearch<int>(nums, 3); // 4
- TrueForAll<T>() : 모든 요소가 조건에 부합하는지(True, False)
private static bool Check(int nums){
return nums > 1;
}
Array.TrueForAll<int>(nums, Check); // False
- Resize<T>() : 크기 재조정
Array.Resize<int>(ref nums, 10); // 1, 5, 4, 2, 3, 0, 0, 0, 0, 0
// nums를 ref(참조)로 전달하는 이유는 배열 안 변수를 변경하기 위함
- ForEach<T>() : 모든 요소에 동일한 작업 부여
- Copy<T>() : 배열 일부를 다른 배열로 복사
int[] sliced = new int[4];
Array.Copy(nums, 0, Sliced, 0, 4); // 1, 5, 4, 2
2) 객체 메서드
- GetLength() : 지정한 차원의 길이를 반환
nums.GetLegnth(0); // 5
3) 프로퍼티
- Length : 배열의 길이 반환
nums.Length; // 5
- Rank : 배열의 차원 반환
nums.Rank // 1
4. 배열 분할
1) System.Range와 .. 키워드
> .. 키워드를 사용하여 System.Range형 객체에 저장하여 사용할 수 있다.
System.Range r1 = 0..3; // 0번째부터 2번째까지
int[] sliced = nums[r1];
2) 다양한 형태
..3 // 0번째부터 2번째까지
1.. // 1번째부터 마지막까지
.. // 0번째부터 마지막까지
..^0 // 0번째부터 마지막까지
5. 가변 배열
> 배열을 요소로 하는 배열
1) 기본문법
데이터형[][]배열명 = new 데이터형[가변배열수][];
int[][] jagged = new int[3][];
2) 사용법
jagged[0] = new int[5] {1, 2, 3, 4, 5};
jagged[1] = new int[] {1, 2, 3, 4};
jagged[2] = new int[] {1, 2};
6. 컬렉션
> 컬렉션이란 같은 성격을 띤 데이터 모음을 담는 자료구조로써 우리가 사용하던 배열(System.Array 클래스)도 컬렉션의 일종이다.
1) ArrayList(리스트)
ArrayList list = new ArrayList();
▲ 생성
list.Add(1);
list.Add(2);
list.Add(3);
// 1, 2, 3
▲ 메서드 Add(), 마지막 요소에 새 요소를 추가
list.RemoveAt(1); // 2 제거 // 1, 3
▲ 메서드 RemoveAt(), 특정 인덱스 요소를 제거
list.Insert(0, 100); // 100을 0번째 인덱스에 삽입 // 100, 1, 3
▲ 메서드 Insert(), 특정 인덱스에 요소를 삽입
2) Queue(큐)
> 요소를 추가할 때는 마지막 인덱스에, 꺼낼 때(사용할 때)는 첫 인덱스로 사용하는 자료구조
Queeu que = new Queue();
que.Enqueue(1); // 추가할 때는 Enqueue를 사용
que.Enqueue(2);
que.Enqueue(3);
// 1, 2, 3
int a = que.Dequeue(); // 사용할 때는 Dequeue를 사용, 1
int b = que.Dequeue(); // 2
int c = que.Dequeue(); // 3
3) Stack(스택)
> Queue와 같은 점은 추가할 때는 마지막 인덱스이지만 꺼낼 때도 마지막 인덱스로 사용한다는 점에서 차이가 있다.
Stack stack = new Stack();
stack.Push(1); // 추가할 때는 Push를 사용
stack.Push(2);
stack.Push(3);
// 1, 2, 3
int a = (int)stack.Pop(); // 사용할 때는 Pop을 사용, 3
int b = (int)stack.Pop(); // 2
int c = (int)stack.Pop(); // 1
4) Hashtable
> 키(key)와 값(value)의 쌍으로 이루어진 자료구조임
Hashtable ht = new Hashtable();
ht["book"] = "책"; // 저장할 때는 배열의 인덱스를 사용하는 것처럼 key 값을 사용
ht["cook"] = "요리사";
Console.WriteLine(ht["book"]) // 사용할 때도 배열의 인덱스처럼 사용한다
Console.WriteLine(ht["cook"])
5) 컬렉션 초기화(ArrayList, Stack, Queue)
int[] arr = {1, 2, 3}; // 배열 생성
ArrayList list = new ArrayList(arr); // 1, 2, 3
Stack stack = new Stack(arr); // 1, 2, 3 // 다만 사용할 때는 3, 2, 1(후입선출)
Queue queue = new Queue(arr); // 1, 2, 3
▲ 배열 객체를 매개변수로 넘긴다
ArrayList list = new ArrayList() {1, 2, 3};
▲ ArrayList만 배열의 도움없이 초기화가 가능하다(컬렉션 초기자)
5.1) 컬렉션 초기화(Hashtable)
Hashtable ht = new Hashtable()
{
{"첫번째", 1},
{"두번째", 2},
{"세번째", 3}
};
▲ ArrayList만 사용했던 컬렉션 초기자를 사용
Hashtable ht = new Hashtable()
{
["첫번째"] = 1,
["두번째"] = 2,
["세번째"] = 3
};
▲ 딕셔너리 초기자를 사용
7. 인덱서(Indexer)
> 인덱서란 인덱스를 사용하여 객체 내 배열이나 배열리스트(ArrayList)등과 같은 컬렉션에 접근하게 해주는 프로퍼티이다
1) 기본문법
한정자 인덱서형 this[형 인덱스명]
{
get{}
set{}
}
2) 사용법
class MyClass{
private int[] arr = new int[5];
public int this[int index] {
get{ return data[index]; }
set{ data[index] = value; }
}
}
MyClass obj = new MyClass();
obj[0] = 0; // 사실상 data[0] = 0; 과 같음
obj[1] = 1; // 사실상 data[1] = 1; 과 같음
8. IEnumerable
> 위에 있는 인덱서를 사용하면 마치 객체를 배열처럼 사용할 수 있었다.
> 이는 foreach문 활용을 극대화할 수 있다는 장점을 가진다.
> 하지만 foreach문은 IEnumerable이라는 인터페이스 형식을 가지고 있어야만 사용할 수 있다.
1) yield
> yield 키워드는 IEnumerator 인터페이스를 구현한 클래스를 생성해준다.
> 따라서 IEnumerable을 상속하지 않아도 yield return 문을 통해 foreach를 사용 가능하다.
using System;
using System.Collections;
namespace Yield{
class MyEnumerator{ // IEnumerable을 상속하지 않음
int[] numbers = {1, 2, 3, 4};
// yield return이 들어가는 메서드는 IEnumerator 클래스가 자동 생성됨
public IEnumerator GetEnumerator(){
yield return numbers[0];
yield return numbers[1];
yield return numbers[2];
yield break;
yield return numbers[3];
}
}
}
class MainApp{
static void Main(string[] args){
var obj = new MyEnumerator();
foreach (int i in obj) Console.WriteLine(i);
}
}
2) 직접 구현
> 직접 구현은 거의 쓰지 않는다고 함
class MyNums : IEnumerable {
int[] data = { 1, 2, 3 };
public IEnumerator GetEnumerator() {
// 배열과 같은 컬렉션들은 이미 IEnumerable을 구현하고 있어, 내부의 GetEnumerator를 사용 가능하다.
return data.GetEnumerator();
}
}
▲ IEnumerable만 상속하여 강제하는 메서드인 GetEnumerator을 구현
class MyNums : IEnumerable, IEnumerator {
int[] data = { 1, 2, 3 };
int position = -1;
public IEnumerator GetEnumerator() {
return this; // 자기 자신을 반환
}
public bool MoveNext() {
position++;
return (position < data.Length);
}
public void Reset() {
position = -1;
}
public object Current {
get { return data[position]; }
}
}
▲ IEnumerable, IEnumerator를 모두 상속하여 강제하는 메서드인 GetEnumerator, MoveNext, Reset과 프로퍼티 Current를 구현
'LMS 7 > 개발일지' 카테고리의 다른 글
| 25.09.29 개발일지 / C# 3 (Chapter13) (0) | 2025.11.11 |
|---|---|
| 25.09.29 개발일지 / C# 2(3) (Chapter11, Chapter12) (0) | 2025.11.11 |
| 25.09.27 개발일지 / C# 2(1) (Chapter07, Chapter08) (0) | 2025.11.11 |
| 25.09.26 개발일지 / C# 1(1) (Chapter02~Chapter03) (0) | 2025.11.11 |
| 25.09.25 개발일지 (0) | 2025.11.04 |