C# [4주차]
인터페이스 (Interface)
인터페이스란 클래스가 구현해야 하는 메서드/속성/이벤트의 계약을 정의한걸 의미한다!
( Class가 어떤 유형인지 정해주는 느낌인거 같은데.. )
인터페이스의 이점
-
코드의 재사용성
인터페이스를 사용하면 다른 Class에서 해당 인터페이스를 구현하여 동일한 기능을 공유할 수 있다! -
다중 상속 제공
C#에서는 Class는 단일 상속만을 지원하지만, 인터페이스는 다중 상속을 지원한다.
-> Class가 여러 인터페이스를 구현(다중 상속)함으로써 여러 개의 기능을 조합할 수 있다! -
유연한 설계
인터페이스를 사용하면 Class와 인터페이스 간에 느슨한 결합을 형성할 수 있다! (Class는 인터페이스를 구현하기만 하면 되므로)
Class의 내부 구현에 대한 변경 없이 인터페이스의 동작을 변경하거나 새로운 인터페이스를 추가할 수 있다!
-> 유연하고 확장 가능한 소프트웨어 설계 가능
인터페이스 사용법
인터페이스 형태로 메서드/속성/이벤트 를 정해주어 이를 상속받은 Class는 무조건 형식에 맞춰 메서드/속성/이벤트를 생성해주어야 한다!
- 인터페이스 선언!
// 인터페이스 1
public interface IPickable
{
// 픽업 메서드 만들어!
void PickUp();
}
// 인터페이스 2
public interface IDroppable
{
// 드랍 메서드 만들어!
void Drop();
}
- 인터페이스 상속!
// 아이템 클래스 ( 다중 상속 )
public class Item : IPickable, IDroppable
{
public string Name { get; set; }
// 정해진 메서드를 만들기
public void PickUp()
{
Console.WriteLine("아이템 {0}을 주웠습니다.", Name);
}
public void Drop()
{
Console.WriteLine("아이템 {0}을 버렸습니다.", Name);
}
}
- 정의한 Class와 인터페이스를 응용해 사용!
// 플레이어 클래스
public class Player
{
// 줍기 가능한 아이템 인터페이스를 상속받은 것만!
public void InteractWithItem(IPickable item)
{
item.PickUp();
}
// 드랍 가능한 아이템 인터페이스를 상속받은 것만!
public void DropItem(IDroppable item)
{
item.Drop();
}
}
// 게임 실행
static void Main()
{
Player player = new Player();
Item item = new Item { Name = "Sword" };
// 아이템 주울 수 있음
player.InteractWithItem(item);
// 아이템 버릴 수 있음
player.DropItem(item);
}
열거형 (Enums)
열거형은 서로 관련된 상수들의 집합을 정의하는 걸 뜻한다. 기본적으로 열거형의 각 상수는 정수 값으로 지정된다!
열거형의 이점
-
가독성
열거형을 사용하면 일련의 연관된 상수들을 명명할 수 있다!
-> 코드의 가독성이 향상, 상수를 사용할 때 잘못된 값 할당 방지 -
자기 문서화(Self-documenting)
열거형은 의미 있는 이름을 사용하여 상수를 명명할 수 있다. -> 코드의 가독성이 향상, 상수의 의미를 명확하게 설명가능 -
스위치 문과의 호환성
열거형을 사용하면 스위치 문에서 다양한 상수 값에 대한 분기를 쉽게 작성할 수 있다.
열거형 사용예제
- 열거형 선언!
enum MyEnum
{
// 값도 지정 가능!
Value1 = 10,
Value2,
Value3
}
- 열거형 사용 + 형변환
MyEnum myEnum = MyEnum.Value1;
int intValue = (int)MyEnum.Value1; // 열거형 값을 정수로 변환
MyEnum enumValue = (MyEnum)intValue; // 정수를 열거형으로 변환
- 스위치문 응용
switch(enumValue)
{
case MyEnum.Value1:
// Value1에 대한 처리
break;
case MyEnum.Value2:
// Value2에 대한 처리
break;
case MyEnum.Value3:
// Value3에 대한 처리
break;
default:
// 기본 처리
break;
}
예외 처리
전에 Node.js에서는 try{}catch(err){} 를 통해 진행하였다. C#에서는 예외 처리 유형에 따라 다른 로직을 적용할 수 있는 것 같다!
try
{
// 예외가 발생할 수 있는 코드
}
catch (ExceptionType1 ex)
{
// ExceptionType1에 해당하는 예외 처리
}
catch (ExceptionType2 ex)
{
// ExceptionType2에 해당하는 예외 처리
}
finally
{
// 예외 발생 여부와 상관없이 항상 실행되는 코드
}
사용자 정의 예외
사용자는 필요에 따라 자신만의 예외 클래스를 작성할 수 있다. ( 나는 이 예외처리를 해봤어요! )
- 사용자 정의 예외 클래스는 Exception 클래스를 상속받아 작성
-> 추가적인 기능이나 정보를 제공할 수 있음
public class NegativeNumberException : Exception
{
// 생성자! 뒤에 : base(message)는 생성자 실행 전에 상속된 Exception을 실행하고 온다는 의미이다!
public NegativeNumberException(string message) : base(message)
{
}
}
try
{
int number = -10;
if (number < 0)
{
throw new NegativeNumberException("음수는 처리할 수 없다네요..");
}
}
catch (NegativeNumberException ex)
{
Console.WriteLine(ex.Message);
}
catch (Exception ex)
{
Console.WriteLine("예외가 발생!! " + ex.Message);
}
값/참조 형
사실 이 부분도 얕은 복사 / 깊은 복사를 배우며 어느정도 개념은 잡혀있었다!
-
값형은 변수에 값을 직접 저장 ( int, float, double, bool, struct 등)
-
참조형은 변수가 데이터에 대한 참조(메모리 주소)를 저장 (클래스, 배열, 인터페이스 등)
-
참조형의 얕은 복사
class MyClass
{
public int Value;
}
MyClass obj1 = new MyClass();
obj1.Value = 10;
// obj2는 obj1과 동일한 객체를 참조
MyClass obj2 = obj1;
obj2.Value = 20;
// 출력 결과: 20
Console.WriteLine(obj1.Value);
박싱(Boxing)과 언박싱(Unboxing)
-
박싱(Boxing) : 값형을 참조형으로 변환하는 과정
-
언박싱(Boxing) : 박싱된 객체를 다시 값형으로 변환하는 과정
-
object와 사용예제
(object는 .NET Common Type System(CTS)의 일부이며, 모든 클래스의 직간접적인 상위 클래스)
using System;
class Program
{
static void Main()
{
// 값형
int x = 10;
int y = x;
y = 20;
// 출력 결과: 10
Console.WriteLine("x: " + x);
// 출력 결과: 20
Console.WriteLine("y: " + y);
// 참조형
int[] arr1 = new int[] { 1, 2, 3 };
int[] arr2 = arr1;
arr2[0] = 4;
// 출력 결과: 4
Console.WriteLine("arr1[0]: " + arr1[0]);
// 출력 결과: 4
Console.WriteLine("arr2[0]: " + arr2[0]);
// 박싱과 언박싱
int num1 = 10;
// 박싱
object obj = num1;
// 언박싱
int num2 = (int)obj;
// 출력 결과: 10
Console.WriteLine("num1: " + num1);
// 출력 결과: 10
Console.WriteLine("num2: " + num2);
/* ------ !!List 활용 예제!!------- */
List<object> myList = new List<object>();
// 박싱: 값 형식을 참조 형식으로 변환하여 리스트에 추가
int intValue = 10;
// int를 object로 박싱하여 추가
myList.Add(intValue);
float floatValue = 3.14f;
// float를 object로 박싱하여 추가
myList.Add(floatValue);
// 언박싱: 참조 형식을 값 형식으로 변환하여 사용
int value1 = (int)myList[0];
float value2 = (float)myList[1];
}
}
-
주의 사항
-
박싱과 언박싱은 값형과 참조형 사이의 변환 작업이므로 성능에 영향을 미칠 수 있음
-
박싱된 값형은 참조로 전달되므로 메모리 오버헤드가 발생할 수 있음
-
박싱된 객체는 힙 영역에 할당되므로 가비지 컬렉션의 대상이 될 수 있음
(-> 메모리 관리에 유의) -
잘못된 형식으로 언박싱하면 런타임 에러가 발생할 수 있음
-
델리게이트 (Delegate)
델리게이트(delegate)는 메서드를 참조하는 타입으로 다른 언어에서는 함수 포인터라는 용어를 사용하기도 한다.
-> 메서드를 매개변수로 전달하거나 변수에 할당하는데 사용가능!
- 델리게이트 선언!
// 매개변수는 델리게이트에 대입할 메서드와 동일해야 함!
delegate int Calculate(int x, int y);
static int Add(int x, int y)
{
return x + y;
}
- 델리게이트 활용!
class Program
{
static void Main()
{
// 메서드 등록
Calculate calc = Add;
// 델리게이트 사용
int result = calc(3, 5);
Console.WriteLine("결과: " + result);
}
}
델리게이트 응용
- 여러 메서드 동시 사용(선언부)
delegate void MyDelegate(string message);
static void Method1(string message)
{
Console.WriteLine("Method1: " + message);
}
static void Method2(string message)
{
Console.WriteLine("Method2: " + message);
}
- 여러 메서드 동시 사용(실행부)
class Program
{
static void Main()
{
// 델리게이트 인스턴스 생성 및 메서드 등록
MyDelegate myDelegate = Method1;
myDelegate += Method2;
/* 출 Method1: Hello!
력 Method2: Hello! */
myDelegate("Hello!");
}
}
- 콜백 받기(선언부)
// 델리게이트 선언
public delegate void EnemyAttackHandler(float damage);
// 적 클래스
public class Enemy
{
// 공격 이벤트
public event EnemyAttackHandler OnAttack;
// 적의 공격 메서드
public void Attack(float damage)
{
// 이벤트 호출
OnAttack?.Invoke(damage);
}
}
// 플레이어 클래스
public class Player
{
// 플레이어가 받은 데미지 처리 메서드
public void HandleDamage(float damage)
{
// 플레이어의 체력 감소 등의 처리 로직
Console.WriteLine("플레이어가 {0}의 데미지를 입었습니다.", damage);
}
}
- 콜백 받기(실행부)
// 게임 실행
static void Main()
{
// 적/플레이어 객체 생성
Enemy enemy = new Enemy();
Player player = new Player();
// 플레이어의 데미지 처리 메서드를 적의 공격 이벤트에 추가 (= 메서드 매핑)
enemy.OnAttack += player.HandleDamage;
// 적의 공격
enemy.Attack(10.0f);
}
람다 (Lambda)
익명 메서드를 만드는 방법으로 메서드의 이름 없이 메서드를 만들 수 있다! ( 이거 화살표 함수자나.. )
- 예제
using System;
// 델리게이트 선언
delegate void MyDelegate(string message);
class Program
{
static void Main()
{
// 델리게이트 인스턴스 생성 및 람다식 할당
MyDelegate myDelegate = (message) =>
{
Console.WriteLine("람다식 첫번째 메시지: " + message);
};
myDelegate += (message) => Console.WriteLine("람다식을 두번째 메시지: " + message);
// 델리게이트 호출
myDelegate("안녕하세요!");
}
}
Func 과 Action
Func과 Action은 델리게이트를 대체하는 미리 정의된 제네릭 형식이다!
- Func는 값을 반환하는 메서드 형식
// Func를 사용하여 두 개의 정수를 더하는 메서드
int Add(int x, int y)
{
return x + y;
}
// Func를 이용한 메서드 호출
// 매개변수 2개와 마지막 반환 값 지정
Func<int, int, int> addFunc = Add;
int result = addFunc(3, 5);
// 결과: 8
Console.WriteLine("결과: " + result);
- Action은 값을 반환하지 않는 메서드 형식 ( void 처럼 실행용! )
void PrintMessage(string message)
{
Console.WriteLine(message);
}
// Action을 이용한 메서드 호출
Action<string> printAction = PrintMessage;
printAction("Hello, World!");
LINQ (Language Integrated Query)
-
.NET 프레임워크에서 제공되는 쿼리 언어 확장 기능!
-
데이터 소스에서 데이터를 쿼리하고 조작하는데 사용
(예: 컬렉션, 데이터베이스, XML 문서 등) -
데이터베이스 쿼리와 유사한 방식으로 데이터를 필터링, 정렬, 그룹화, 조인 등 다양한 작업을 수행
-
쿼리를 던지고 필터링하느라 성능에 영향을 많이 미칠 수 있음!
-
기본 형태
var result = from 변수 in 데이터소스
[where 조건식]
[orderby 정렬식 [, 정렬식...]]
[select 식];
- 예제
// 데이터 소스 정의 (컬렉션)
List<int> numbers = new List<int> { 1, 2, 3, 4, 5 };
// 쿼리 작성 (선언적인 구문)
// + 튜플 형식으로 여러 변수를 컨트롤할 수 있음!
var evenNumbers = from num in numbers
where num % 2 == 0
select num;
// 쿼리 실행 및 결과 처리
foreach (var num in evenNumbers)
{
Console.WriteLine(num);
}
-
var
키워드는 결과 값의 자료형을 자동으로 추론! -
from
절에서는 데이터 소스를 지정 -
where
절은 선택적으로 사용하며, 조건식을 지정하여 데이터를 필터링 -
orderby
절은 선택적으로 사용하며, 정렬 방식을 지정 -
select
절은 선택적으로 사용하며, 조회할 데이터를 지정
고급 자료형 기능
Nullable
C#에서 null 값을 가질 수 있는 값형에 대한 특별한 형식으로 약간 Prisma 썼을 때 문법과 똑같다!
(변수 Type + “?”)
- Nullable 선언
int? nullableInt = null;
double? nullableDouble = 3.14;
bool? nullableBool = true;
- Nullable 활용(메서드)
// 값 할당 및 접근
nullableInt = 10;
int intValue = nullableInt.Value;
// null 값 검사
if (nullableDouble.HasValue)
{
Console.WriteLine("nullableDouble 값: " + nullableDouble.Value);
}
// null 병합 연산자 사용
// nullableInt ?? 0과 같이 사용되며, nullableInt가 null이면 0을 반환
int nonNullableInt = nullableInt ?? 0;
Console.WriteLine("nonNullableInt 값: " + nonNullableInt);
문자열 빌더 (StringBuilder)
-
Append(), Insert(), Replace(), Remove() 등 다양한 메서드를 제공하여 문자열에 대한 추가, 삽입, 치환, 삭제 작업을 수행할 수 있음
-
내부 버퍼를 사용하여 문자열 조작을 수행하므로 크기를 동적으로 조정할 수 있음 (= 추가적인 메모리 할당이 발생 X)
(StringBuilder의 용량이 무제한은 아니며 최대 용량을 넘어서게 되면 새 공간이 자동 할당되 용량이 두 배로 증가..) -
주요 메서드
-
Append: 문자열을 뒤에 추가
-
Insert: 문자열을 지정한 위치에 삽입
-
Remove: 지정한 위치에서 문자열을 제거
-
Replace: 문자열의 일부를 다른 문자열로 대체
-
Clear: StringBuilder의 내용을 모두 지움
-
-
메서드 활용
StringBuilder sb = new StringBuilder();
// 문자열 추가
sb.Append("Hello");
// 문자열 삽입
sb.Insert(2, ", ");
// 문자열 치환
sb.Replace("llo", "is C#");
// 문자열 삭제
sb.Remove(2, 1);
// 완성된 문자열 출력
string result = sb.ToString();
// He is C#
Console.WriteLine(result);
한줄 평 + 개선점
-
C# 문법에 대해선 대부분 끝났고, 남은건 알고리즘이다!
-
새로운 언어의 새로운 규칙을 배워가는 것도 나름 즐겁다!