프로퍼티
class 클래스_이름
{
데이터_형식 필드_이름;
접근_한정자 데이터_형식 프로퍼티_이름
{
get
{
return 필드_이름;
}
set
{
필드_이름 = value;
}
}
}
get { ••• }과 set { ••• }을 일컬어 접근자 라고합니다. get 집근자는 필드로부터 값을 읽어오고 set 접근자는 필드에 값을 할당합니다.
set 접근자 안에 있는 value는 암묵적으로 매개변수로 간주합니다.
[장점]
- 캡슐화: 객체의 내부 데이터를 외부에서 직접 접근하지 못하도록 숨겨서 데이터를 보호할 수 있습니다.
- 유효성 검사: set 접근자에서 값을 설정하기 전에 유효성 검사를 수행하여 잘못된 값이 저장되는 것을 방지할 수 있습니다.
- 계산된 속성: get 접근자에서 다른 속성이나 데이터를 기반으로 값을 계산하여 반환할 수 있습니다.
- 코드 간결화: get/set 메서드를 사용하는 것보다 코드가 간결해집니다.
class Person
{
private string name; // private으로 선언된 name 필드
public string Name // name 필드에 접근하기 위한 Name 프로퍼티
{
get { return name; } // Name 프로퍼티 값을 읽어올 때 name 필드 값을 반환
set { name = value; } // Name 프로퍼티 값을 설정할 때 name 필드에 value 값을 저장
}
}
=> Name 프로퍼티는 name 필드에 접근하기 위한 "문" 역할을 합니다. 외부에서 person.Name으로 접근하면 get/set 접근자를 통해 name 필드에 접근하게 됩니다.
class Person
{
private int age;
public int Age
{
get { return age; }
set
{
if (value < 0)
age = 0;
else
age = value;
}
}
}
- get 접근자: age 필드의 값을 반환합니다.
- set 접근자: age 필드에 값을 설정하기 전에 값이 0보다 작은지 확인하고, 0보다 작으면 0을 저장하고, 그렇지 않으면 받아온 값을 저장합니다.
자동 구현 프로퍼티
** 그저 get 접근자와 set접근자 뒤에 (;)만 붙여주면 됩니다.
using System;
namespace AutoImplementedProperty
{
class BirthdayInfo
{
public string Name { get; set; } = "Unknown";
public DateTime Birthday { get; set; } = new DateTime(1, 1, 1);
public int Age
{
get
{
return new DateTime(DateTime.Now.Subtract(Birthday).Ticks).Year;
}
}
}
class MainApp
{
static void Main(string[] args)
{
BirthdayInfo birth = new BirthdayInfo();
Console.WriteLine($"Name : {birth.Name}");
Console.WriteLine($"Birthday : {birth.Birthday.ToShortDateString()}");
Console.WriteLine($"Age : {birth.Age}");
birth.Name = "서현";
birth.Birthday = new DateTime(1991, 6, 28);
Console.WriteLine($"Name : {birth.Name}");
Console.WriteLine($"Birthday : {birth.Birthday.ToShortDateString()}");
Console.WriteLine($"Age : {birth.Age}");
}
}
}
=> DateTime.Subtract(): 두 날짜의 차이를 계산합니다.
=> ToShortDateString(): 날짜를 간단한 형식으로 변환합니다.
프로퍼티와 생성자
프로퍼티를 이용해 객체를 생성할 때 각 필드를 초기화하는 방법
클래스_이름 인스턴스 = new 클래스_이름()
{
프로퍼티1 = 값,
프로퍼티2 = 값,
프로퍼티3 = 값
};
BirthdayInfo birth = new BirthdayInfo()
{
Name = "서현",
Birthday = new DateTime(1991, 6, 28)
};
초기화 전용 자동 구현 프로퍼티
* init 접근자를 사용 : 자동 구현 프로퍼티를 선언하면서 set접근자 대신 init 접근자를 명시하면 된다.
public class Transaction
{
public string From {get; init;}
public string To {get; init;}
public int Amount {get; init;}
}
With를 이용한 레코드 복사
레코드 형식을 위한 복사 생성자를 자동으로 작성합니다. 복사 생성자는 protected로 선언되기 때문에 명시적으로 호출할 수 없고, with 식을 이용해야 합니다.
RTransaction tr1 = new RTranction {From = "Alice", To = "Bob", Amount = 100);
RTransaction tr2 = tr1 with {To = "Charlie"};
// tr1의 모든 상태를 복사한 다음 -> To 프로퍼티 값만 Charlie로 수정
레코드 객체
레코드의 상태를 비교하는 Equals () 메서드를 자동으로 구현합니다. 다음 표에서 왼쪽에는 클래스 객체 상태 비교를 위한 Equals () 메서드 오버라이딩 예제가 나타나 있고, 오른쪽에는 평범한 레코드 선언 예제가 있습니다. 레코드는 참조 형식이지만 값 형식처럼 Equals () 메서드를 구현하지 않아도 비교가 가능합니다.
무명 형식
무명형식은 형식의 선언과 동시에 인스턴스를 할당합니다.
중괄호 {와 } 사이에 임의의 프로퍼티 이름을 적고 값을 할당하면 그대로 새 형식의 프로퍼티가 됩니다.
var myInstance = new {Name = "박상현", Age = "17"};
** 무명형식의 인스턴스는 여느 객체처럼 프로퍼티에 접근하여 사용할 수 있습니다.
** 주의할 점 : 무명 형식의 프로퍼티에 할당된 값은 변경불가능 읽기만 가능합니다.
인터페이스의 프로퍼티
interface 인터페이스_이름
{
public 형식 프로퍼티_이름1
{
get; set;
}
public 형식 프로퍼티_이름2
{
get; set;
}
// ...
}
프로퍼티를 가진 인터페이스와 이를 상속하는 파생클래스의 예
interface IProduct
{
string ProductName
{
get;
set;
}
}
class Product : IProduct
{
private string productName;
public string ProductName // 파생 클래스는 기반 인터페이스에 선언된 모든 프로퍼티를 구현해야 합니다.
{
get{return productName;}
set{productName = value;}
}
}
추상 클래스의 프로퍼티
abstract class 추상_클래스_이름
{
abstract 데이터_형식 프로퍼티_이름
{
get;
set;
}
}
System.Array
System.Array의 특성과 메서드를 파악하면 배열의 특성과 메서드를 알게 되는 셈이며, 배열의 내부 데이터를 원하는 순서대로 정렬한다든가, 특정 데이터를 배열 속에서 찾아내는 작업들을 말합니다.
BinarySearch, TrneForAll, Findlndex, Resize, ForEach처럼 뒤에 〈T〉를 붙이고 다니는 메서드로
〈T〉는 형식 매개변수Type Parameter라고 하는데, 이들 메서드를 호출할 때는 T 대신 배열의 기반 자료 형을 인수로 입력하면 컴파일러가 해당 형식에 맞춰 동작하도록 메서드를 컴파일합니다.
배열 분할하기
System.Range는 이름이 나타내는 것처럼, 시작 인덱스와 마지막 인덱스를 이용해서 범위를 나타냅니다.
System.Range 객체를 생성할 때는 다음과 같이 연산자를 이용합니다. 연산자는 다음 예제 코드에서처럼 왼쪽에는 시작 인덱스, 오른쪽에는 마지막 인덱스 가옵니다
System.Range r1 = 0..3; // 시작 인덱스..마지막 인덱스
int[] sliced = scores[0..3]; // [와 ] 사이에 직접.. 연산자를 입력하면 코드가 더간결해집니다.
// 첫 번째(0) 요소부터 세 번째(2) 요소까지
int[] sliced3 = scores[..3];
// 두 번째(1) 요소부터 마지막 요소까지
int[] sliced4 = scores[1..];
// 전체
int[] sliced5 = scores[..];
// System.Index idx = ^1;
int[] sliced5 = scores[..idx];
int[] sliced6 = scores[..^1]; // System.Index 객체를 생성하지 않고 ^연산자를 직접입력하면 코드가 간결해진다.
컬렉션
컬렉션이란, 같은 성격을 띤 데이터의 모음을 담는 자료구조입니다.
ArrayList가 가장 배열과 닮은 컬렉션이라고 할 수 있습니다.
** Add(), RemoveAt() , Insert() 메서드가 가장 중요합니다.
ArrayList list = new ArrayList();
list.Add(10);
list.Add(20);
list.Add(30);
list.RemoveAt(1); // 20을 삭제
list.Insert(1, 25); // 25를 1번 인덱스에 삽입. 즉, 10과 30 사이에 25를 삽입
Queue
First In - First Out (FIFO)구조
Queue 자료구조는 데이 터나 작업을 차례대로 입력해 뒀다가 입력된 순서대로 하나씩 꺼내 처리하기 위해 사용됩니다.
배열이 나 리스트가 원하는 위치에 자유롭게 접근하는 반면에 Queue는 입력은 오직 뒤에서, 출력은 앞에 서만 이루어집니다. OS에서 CPU가 처리해야 할 작업을 정리할 때, 프린터가 여러 문서를 출력할 때, 인터넷 동영상 스트리밍 서비스에서 콘텐츠를 버퍼링 할 때 등등 쓰인다.
Queue에 데이터를 입력하는 것은 Enqueue() 메서드를 이용합니다.
Queue que = new Queue();
que.Enqueue(1);
que.Enqueue(2);
que.Enqueue(3);
que.Enqueue(4);
que.Enqueue(5);
Queue에서 데이터를 꺼낼 때 Dequeue() 메서드를 이용합니다.
int a = que.Dequeue();
using System;
using System.Collections;
namespace UsingQueue
{
class MainApp
{
static void Main(string[] args)
{
Queue que = new Queue();
que.Enqueue(1);
que.Enqueue(2);
que.Enqueue(3);
que.Enqueue(4);
que.Enqueue(5);
while(que.Count>0)
{
Console.WriteLine(que.Dequeue());
}
}
}
}
Stack
Last In - First Out (LIFO) 구조
Stack에 데이터를 넣을 때는 Push () 메서드를 이용하고, 데이터를 꺼낸 때는 Pop () 메서드를 이용합니다.
Push() 메서드는 데이터를 위에 쌓고’, Pop() 메서드는 가장 위에 쌓여 있는 데이터를 꺼냅니다’. Pop ()을 호출하여 데이터를 Stack에서 꺼내고 나면 고 데이터는 컬렉션에서 제거되고 그 아래에 있던 데이터가 가장 위로 올라옵니다.
Stack stack = new Stack();
stack.Push(1); // 최상위 데이터는1
stack.Push(2); // 최상위 데이터는2
stack.Push(3); // 최상위 데이터는3
int a = (int)stack.Pop(); // 최상위 데이터는 다시 2
using System;
using System.Collections;
namespace UsingStack
{
class MainApp
{
static void Main(string[] args)
{
Stack stack = new Stack();
stack.Push(1);
stack.Push(2);
stack.Push(3);
stack.Push(4);
stack.Push(5);
while( stack.Count > 0)
{
Console.WriteLine(stack.Pop());
}
}
}
}
Hashtable
키(Key)와 값(Value)의 쌍으로 이루어진 데이터를 다룰 때 사용합니다.
Hashtable ht = new Hashtable();
ht["book"] = "책";
ht["cook"] = "요리사";
ht["tweet"] = "지저귀다";
Console.WriteLine(ht["book"]);
Console.WriteLine(ht["cook"]);
Console.WriteLine(ht["tweet"]);
** 배열의 위치를 키 데이터로 값을 찾는 데 사용
Hashtable은 키를 이용해서 단번에 데이터가 저장된 컬렉션 내의 주소를 계산해 냅니다. 이 작업을 "해싱(Hashing)"이라고 한다.
using System;
using System.Collections;
using static System.Console;
namespace UsingHashtable
{
class MainApp
{
static void Main(string[] args)
{
Hashtable ht = new Hashtable();
ht["하나"] = "one";
ht["둘"] = "two";
ht["셋"] = "three";
ht["넷"] = "four";
ht["다섯"] = "five";
WriteLine(ht["하나"]);
WriteLine(ht["둘"]);
WriteLine(ht["셋"]);
}
}
}
Hashtable 초기화
Hashtable 초기화할 때는 딕셔너리 초기자를 이용한다.
Hashtable ht = new Hashtable()
{
["하나"] = 1, // ;가 아니라 , 를 이용하여 항목을 구분
["둘"] = 2,
["셋"] = 3
};
또 다른 방법
Hashtable ht2 = new Hashtable()
{
{"하나",1},
{"둘",2},
{"셋",3}
};
컬렉션 초기화
ArrayList, Queue, Stack은 배열의 도움을 받아 간단하게 초기화를 수행할 수 있습니다. 이들 컬렉션의 생성자를 호출할 때 배열 객체를 매개변수로 넘기면 컬렉션 객체는 해당 배열을 바탕으로 내부 데이터를 채웁니다.
int[] arr = {123, 456, 789};
ArrayList list = new ArrayList(arr); // 123, 456, 789
Stack stack = new Stack(arr); // 789, 456, 123
Queue queue = new Queue(arr); // 123, 456, 789
** ArrayList는 배열의 도움 없이 직접 컬렉션 초기자를 이용해서 초기화할 수 있습니다.
// 컬렉션 초기자는 생성자를 호출할 때,
// 생성자 뒤에 {와} 사이에 컬렉션 요소의 목록을 입력하여 사용합니다.
ArrayList list2 = new ArrayList(); {11,22,33};
안타깝게도 Stack과 Queue는 컬렉션 초기지를 이용할 수 없습니다. 컬렉션 초기지는 IEnumerable 인터페이스 와 Add() 메서드를 구현하는 컬렉션만 지원하는데, 이 두 컬렉션은 IEnumerable은상 속하지만 Add() 메서드는 구현하지 않기 때문입니다.
using System;
using System.Collections;
using static System.Console;
namespace InitializingCollections
{
class MainApp
{
static void Main(string[] args)
{
int[] arr = { 123, 456, 789 }; // 배열을 이용한 컬렉션 초기화
ArrayList list = new ArrayList(arr);
foreach (object item in list)
WriteLine($"ArrayList : {item}");
WriteLine();
Stack stack = new Stack(arr);
foreach (object item in stack)
WriteLine($"Stack : {item}");
WriteLine();
Queue queue = new Queue(arr);
foreach (object item in queue)
WriteLine($"Queue : {item}");
WriteLine();
ArrayList list2 = new ArrayList() { 11, 22, 33 }; // 컬렉션 초기자를 이용한 컬렉션 초기화
foreach (object item in list2)
WriteLine($"ArrayList2 : {item}");
WriteLine();
}
}
}
인덱서
인덱서 Indexer는 인덱스를 이용해서 객체 내의 데이터에 접근하게 해주는 프로퍼티라고 생각하면 이해하기 쉽습니다. 객체를 마치 배열처럼 사용할 수 있다.
class 클래스_이름
{
한정자 인덱서_형식 this[형식 index]
{
get
{
// index를 이용하여 내부 데이터 반환
}
set
{
// index를 이용하여 내부 데이터 저장
}
}
}
** 인덱서는 인덱스를 통해 객체 내 데이터에 접근하게 해 줍니다.
class MyList
{
private int[] array;
public MyList()
{
array = new int[3];
}
public int this[int index] // this : 인덱서
{
get
{
return array[index];
}
set
{
if(index >= array.Length)
{
Array.Resize<int>(ref array, index +1);
Console.WriteLine("Array Resized : {0}", array.Length);
}
array[index] = value;
}
}
}
=> 다음의 MyList는 내부에 정수 형식 배열을 갖고 있고, 인덱서를 통해 이 배열에 접근합니다. 인덱서를 통해 데이터를 저장하려는 시도가 이루어질 때 지정한 인덱스 보다 배열의 크기가 작다면 인덱스에 맞춰 배열의 크기를 재조정합니다.
=> 인덱서도 프로퍼티처럼 객체 내 의 데이터에 접근할 수 있도록 이는 통로입니다. 프로퍼티와 다른 점이라면 ‘인덱스’를 이용한다는 사실입니다.
foreach가 가능한 객체
foreach 문은 for 문처럼 요소의 위치를 위한 인덱스 변수를 선언할 필요가 없습니다. 세미콜론을 2개나 넣지 않아도 되고, 조건문이나 증감식을 쓰지 않아도 됩니다. for 문을 이용한 코드에 비해 foreach 문을 이용한 코드는 쓰기도 좋고, 읽기도 좋습니다.
** 배열이나 리스트 같은 컬렉션에서만 사용가능하다.
foreach 구문은 IEnumerable을 상속 하는 형식만 지원합니다. IEnumerable 인터페이스가 갖고 있는 메소드는 다음과 같이 난 하나뿐입니다.
메서드 | 설명 |
IEnumerable GetEnumerator() | IEnumerable 형식의 객체를 반환 |
GetEnumerator()는 IEnumerable 인터페이스를 상속하는 클래스의 객체를 반환해야 하는 데요. yield 문을 이용하면 IEnumerable 를 상속하는 클래스를 따로 구현하지 않아도 컵파일러 가 자동으로 해당 인터페이스를 구현한 클래스를 생성해줍니다. yield return 문은 현재 메소드 (GetEnumerator() )의 선행을 일시 정지해놓고 호출자에게 결과를 반환합니다. 메소드가 다시 호출되면, 일시 정지된 실행을 복구하여 yield return 또는 yield break 문을 만날 때까지 나머지 작업을 신행하게 됩니다.
[ IEnumerator 인터페이스의 메소드 및 프로퍼티 목록]
'LAB > C#' 카테고리의 다른 글
C# ) C# 기초 (스레드 , 태스크 ,네트워크 프로그래밍) (0) | 2024.11.02 |
---|---|
C# ) C# 기초 5 (대리자, 이벤트) (0) | 2024.11.01 |
C# ) C# 기초 4 (일반화 프로그래밍, 예외처리) (0) | 2024.11.01 |
C# ) C# 기초 2 (구조체 , 튜플, 인터페이스, 추상클래스) (0) | 2024.10.30 |
C#) C# 기초 (데이터 형식 , 자료구조, 클래스) (0) | 2024.10.30 |