인터페이스
를 사용하여 다중 상속이 필요한 경우에도 유사한 기능을 구현할 수 있음I
를 붙여줌interface IMyInterface
{
void Method1();
int Method2(string str);
}
class MyClass : IMyInterface
{
public void Method1()
{
// 구현
}
public int Method2(string str)
{
// 구현
return 0;
}
}
특징 | 인터페이스 (Interface) | 추상 클래스 (Abstract Class) |
---|---|---|
목적 | 여러 클래스에 공통된 동작을 강제하기 위함 | 상속을 통해 기본 기능을 제공하고 확장하기 위함 |
멤버 구성 | 메서드, 속성, 이벤트(모두 구현 없이 선언만 가능, C# 8부터는 일부 구현 가능) | 필드, 생성자, 메서드(추상 & 일반 메서드 모두 가능) |
구현 여부 | 메서드의 본문(구현부)을 가질 수 없음 (C# 8 이후 일부 가능) | 일반 메서드와 추상 메서드 모두 가능 |
상속 관계 | 다중 구현 가능 (여러 개의 인터페이스 사용 가능) | 단일 상속만 가능 |
인스턴스 생성 | 인스턴스 생성 불가 | 인스턴스 생성 불가 |
접근 제한자 | 기본적으로 public (접근 제한자 지정 불가) | public , protected , private 등 가능 |
사용 예시 | 여러 클래스에 동일한 동작을 강제할 때 (예: 동물들이 모두 "소리 내기") | 기본 기능을 제공하면서 세부 구현을 자식 클래스에 맡길 때 (예: 동물의 기본 행동 제공) |
상황 | 인터페이스 사용 | 추상 클래스 사용 |
---|---|---|
여러 클래스가 공통 기능을 가져야 할 때 | ✅ (예: IDisposable , IComparable ) | ✅ (예: Animal → Dog , Cat ) |
기본 구현 없이 규칙만 강제할 때 | ✅ (구현 없이 메서드 시그니처만 정의) | ❌ (추상 클래스는 일부 구현을 포함 가능) |
기본 기능(메서드, 필드)을 제공할 때 | ❌ (메서드 구현 불가능) | ✅ (일반 메서드 구현 가능) |
다중 상속(다중 구현)이 필요할 때 | ✅ (여러 인터페이스 구현 가능) | ❌ (클래스는 단일 상속만 가능) |
기존 클래스를 확장할 때 | ❌ (인터페이스는 기본 구현이 없어서 확장 불리) | ✅ (추상 클래스는 기존 기능을 확장하기 좋음) |
가독성:
자기 문서화:
스위치 문과의 호환성:
switch(enumValue)
{
case MyEnum.Value1:
// Value1에 대한 처리
break;
case MyEnum.Value2:
// Value2에 대한 처리
break;
case MyEnum.Value3:
// Value3에 대한 처리
break;
default:
// 기본 처리
break;
// 열거형 정의 및 상수값 지정
enum MyEnum
{
Value1 = 10,
Value2, //앞에 10을 지정했으므로 Value2는 자동으로 11이 된다.
Value3 = 20
}
// 열거형 사용
MyEnum myEnum = MyEnum.Value1;
// 열거형 형변환
int intValue = (int)MyEnum.Value1; // 열거형 값을 정수로 변환
MyEnum enumValue = (MyEnum)intValue; // 정수를 열거형으로 변환
try-catch
try
{
// 예외가 발생할 수 있는 코드
}
catch (ExceptionType1 ex)
{
// ExceptionType1에 해당하는 예외 처리
}
catch (ExceptionType2 ex)
{
// ExceptionType2에 해당하는 예외 처리
}
finally
{
// 예외 발생 여부와 상관없이 항상 실행되는 코드
}
try
블록 내에서 예외가 발생할 수 있는 코드를 작성하고, catch
블록에서 예외를 처리한다.catch
블록은 위부터 순서대로 실행되며, 여러 개의 catch
블록을 사용하여 다양한 예외 타입을 처리할 수 있다.static void Main()
{
try
{
Console.Write("숫자를 입력하세요: ");
string input = Console.ReadLine();
int number = int.Parse(input); // 입력값이 숫자가 아니면 예외 발생!
int result = 100 / number; // 0이면 예외 발생!
Console.WriteLine($"결과: {result}");
}
catch (FormatException) // 잘못된 입력 처리
{
Console.WriteLine("오류: 숫자를 입력해야 합니다!");
}
catch (DivideByZeroException) // 0으로 나누는 경우 처리
{
Console.WriteLine("오류: 0으로 나눌 수 없습니다!");
}
catch (Exception ex) // 모든 예외를 처리하는 기본 catch (맨 마지막에 둠)
{
Console.WriteLine($"알 수 없는 오류 발생: {ex.Message}");
}
}
Exception
클래스를 상속받아 새로운 예외 클래스를 정의하면 된다. public class NegativeNumberException : Exception
{
//생성자 : 이니셜라이저
public NegativeNumberException(string message) : base(message)
{
//이니셜라이저는 생성자가 실행되기 전에 처리할 것들을 명시한다.
//base == Exception(부모클래스)
//즉 Exception의 생성자를 먼저 실행하고
//NegativeNumberException의 생성자를 실행한다.
}
}
static void Main(string[] args)
{
try
{
int number = -10;
if (number < 0)
{
//throw: 오류를 일부러 일으킴
throw new NegativeNumberException("음수는 처리할 수 없습니다.");
}
}
catch (NegativeNumberException ex)
{
Console.WriteLine(ex.Message);
}
catch (Exception ex)
{
Console.WriteLine("예외가 발생했습니다: " + ex.Message);
}
}
if
문과 try-catch
의 차이점
비교 항목 | if 문 (예방적 검사) | try-catch (예외 처리) |
---|---|---|
오류 발생 방식 | 예외가 발생하기 전에 조건을 확인해서 방지 | 예외가 발생한 후 잡아서 처리 |
사용 목적 | 예상 가능한 오류를 방지할 때 | 예상하지 못한 오류도 안전하게 처리할 때 |
성능 | 비교적 빠름 (예외가 없으면 코드 실행 속도 영향 없음) | 예외가 발생하면 성능 비용이 큼 |
처리 방식 | 조건문을 사용하여 직접 해결 | 예외가 발생하면 자동으로 호출 스택을 따라감 |
필수 여부 | 단순한 오류 방지는 if 문으로 충분 | 파일, 네트워크, 데이터베이스 오류는 try-catch 가 필수 |
if
문을 사용해 예외처리를 해도 괜찮은 경우 :if (age > 0) {}
(나이는 0보다 커야 함)if (index >= 0 && index < array.Length) {}
if (File.Exists(path)) {}
try-catch
문을 사용해야 하는 경우 :WebClient.DownloadString(url)
File.ReadAllText(path)
int.Parse(input)
SqlConnection.Open()
char
, int
, float
, double
, bool
등의 기본 데이터 타입들이 값형에 해당.String
, 클래스
, 배열
, 인터페이스
등이 참조형에 해당.object
:
- 모든 클래스의 직간접적인 상위 클래스
- 모든 클래스는 object
에서 상속되며, object
는 모든 형식을 참조할 수 있는 포괄적인 타입이다.
// 박싱과 언박싱
int num1 = 10;
object obj = num1; // 박싱
int num2 = (int)obj; // 언박싱
Console.WriteLine("num1: " + num1); // 출력 결과: 10
Console.WriteLine("num2: " + num2); // 출력 결과: 10
// 리스트 활용 예제
List<object> myList = new List<object>();
// 박싱: 값 형식을 참조 형식으로 변환하여 리스트에 추가
int intValue = 10;
myList.Add(intValue); // int를 object로 박싱하여 추가
float floatValue = 3.14f;
myList.Add(floatValue); // float를 object로 박싱하여 추가
// 언박싱: 참조 형식을 값 형식으로 변환하여 사용
int value1 = (int)myList[0]; // object를 int로 언박싱
float value2 = (float)myList[1]; // object를 float로 언박싱
//델리게이트 선언
delegate void MyDelegate(string message);
//메서드1
static void Method1(string message)
{
Console.WriteLine("Method1: " + message);
}
//메서드2
static void Method2(string message)
{
Console.WriteLine("Method2: " + message);
}
class Program
{
static void Main()
{
// 델리게이트 인스턴스 생성 및 메서드1 등록
MyDelegate myDelegate = Method1;
// += 를 통해 여러 메서드를 등록할 수 있다.
myDelegate += Method2;
// 델리게이트 호출
myDelegate("Hello!");
// 사용자가 키를 누를 때까지 대기 (종료 방지)
Console.ReadKey();
}
}
=
) 사용 불가능 (+=
, -=
는 가능)// 델리게이트 선언
public delegate void EnemyAttackHandler(float damage);
// 적 클래스
public class Enemy
{
// 공격 이벤트
public event EnemyAttackHandler OnAttack;
// 적의 공격 메서드
public void Attack(float damage)
{
// 이벤트 호출
OnAttack?.Invoke(damage);
// null 조건부 연산자
// null 참조가 아닌 경우에만 멤버에 접근하거나 메서드를 호출
}
}
// 플레이어 클래스
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);
}
OrderBy()
를 사용하며 람다식을 쓴 적이 있다 👉 2025.01.13 (월) TIL// 람다의 기본 형식
(parameter_list) => expression
// 람다를 통해 익명 메서드를 만들어 델리게이트 calc에 저장
Calculate calc = (x, y) =>
{
return x + y;
};
// (x, y)는 메서드의 매개변수, => 뒤의 x, y는 메서드의 반환값이다.
Calculate calc = (x, y) => x + y;
// 매개변수가 필요없는 메서드라면 괄호는 비워둔다.
MyDelegate myDelegate = () =>
{
Console.WriteLine("람다식을 통해 전달된 메시지:");
}
// 델리게이트 선언
delegate void MyDelegate(string message);
class Program
{
static void Main()
{
// 델리게이트 인스턴스 생성 및 람다식 할당
MyDelegate myDelegate = (message) =>
{
Console.WriteLine("람다식을 통해 전달된 메시지: " + message);
};
// 델리게이트 호출
myDelegate("안녕하세요!");
Console.ReadKey();
}
}
[결과]
람다식을 통해 전달된 메시지: 안녕하세요!
Func
과 Action
Func
:Func<int, string>
는 int
를 입력으로 받아 string
을 반환하는 메서드// Func를 사용하여 두 개의 정수를 더하는 메서드
int Add(int x, int y)
{
return x + y;
}
// Func를 이용한 메서드 호출
Func<int, int, int> addFunc = Add;
int result = addFunc(3, 5);
Console.WriteLine("결과: " + result);
Action
:Action<int, string>
은 int
와 string
을 입력으로 받고, 아무런 값을 반환하지 않는 메서드// Action을 사용하여 문자열을 출력하는 메서드
void PrintMessage(string message)
{
Console.WriteLine(message);
}
// Action을 이용한 메서드 호출
Action<string> printAction = PrintMessage;
printAction("Hello, World!");
// 게임 캐릭터 클래스
class GameCharacter
{
// float값을 받고, 반환하는 값이 없는 메서드들을
// 연결할 수 있는 델리게이트 healthChangedCallback을 만든다.
private Action<float> healthChangedCallback;
// 클래스 내부에서만 접근 가능한 health 변수
private float health;
// health 변수에 접근하기 위한 프로퍼티를 만들어줌
public float Health
{
get { return health; }
// healthChangedCallback는 체력이 변경될 때마다 호출됨.
set
{
health = value;
healthChangedCallback?.Invoke(health);
}
}
// Action<float> 형식의 callback을 하나 받고
public void SetHealthChangedCallback(Action<float> callback)
{
// callback에 담긴 메서드를 healthChangedCallback에 연결해줌
healthChangedCallback = callback;
}
}
// 게임 캐릭터 생성 및 상태 변경 감지
GameCharacter character = new GameCharacter(); // 캐릭터 생성
// 캐릭터 클래스 안의 SetHealthChangedCallback 메서드를 호출
// 람다로 Action<float> 형식의 임시 메서드를 만들어 인자로 전달함
character.SetHealthChangedCallback(health =>
{
if (health <= 0)
{
Console.WriteLine("캐릭터 사망!");
}
});
// 캐릭터의 체력 변경
character.Health = 0;
var result = from 변수 in 데이터소스
[where 조건식]
[orderby 정렬식 [, 정렬식...]]
[select 식];
var
: 결과 값의 자료형을 자동으로 추론from
: 데이터 소스를 지정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);
}
from num in numbers
: 우린 numbers에 있는 것들을 num이라고 부를 건데,where num % 2 == 0
: num이 2의 배수라면select num
: 우린 그 2의 배수인 애들을 num으로 가져오겠다.null
이란?Nullable
란??
연산자를 사용하여 선언// Nullable 형식 변수 선언
int? nullableInt = null;
double? nullableDouble = 3.14;
bool? nullableBool = true;
// 이런 형식도 가능하지만 보통 int? 형식을 선호한다.
Nullable<int> a = 10;
??
연산자 (Null Coalescing Operator)int? num = null;
int result = num ?? 100; // num이 null이면 100 사용
Console.WriteLine(result); // 출력: 100
?.
연산자 (Null-Conditional Operator)int? number = null;
Console.WriteLine(number?.ToString()); // 출력 없음 (null 반환)
// 델리게이트 실습 때도 ?. 연산자가 사용되었다.
public void Attack(float damage)
{
// null 참조가 아니라면 이벤트 호출
OnAttack?.Invoke(damage);
}
??=
연산자 (Null Coalescing Assignment)int? num = null;
num ??= 50; // num이 null이면 50 할당
Console.WriteLine(num); // 출력: 50
StringBuilder
란?Append()
, Insert()
, Replace()
, Remove()
등과 같은 다양한 메서드를 통해 문자열에 대한 추가, 삽입, 치환, 삭제 작업을 수행할 수 있음StringBuilder
는 내부 버퍼를 사용하여 문자열 조작을 수행하므로 크기를 동적으로 조정할 수 있다.StringBuilder
의 용량이 무제한은 아니며, 최대 용량이 지정되고 최대 용량을 넘어서게 되면 새 공간이 자동으로 할당되고 용량이 두 배로 증가한다.string
과 StringBuilder
의 차이점 :
비교 항목 | string (불변) | StringBuilder (가변) |
---|---|---|
문자열 수정 가능? | ❌ 불가능 (새로운 객체 생성) | ✅ 가능 (기존 객체에서 직접 변경) |
메모리 효율성 | ❌ 낮음 (변경할 때마다 새 객체 생성) | ✅ 높음 (같은 객체에서 변경) |
속도 | 느림 (반복 수정 시 비효율적) | 빠름 (반복 수정 최적화) |
사용 예시 | 문자열 변경이 적을 때 | 문자열 변경이 많을 때 |
Append
: 문자열을 뒤에 추가Insert
: 문자열을 지정한 위치에 삽입Remove
: 지정한 위치에서 문자열을 제거Replace
: 문자열의 일부를 다른 문자열로 대체Clear
: StringBuilder의 내용을 모두 지움StringBuilder sb = new StringBuilder();
// 문자열 추가
sb.Append("Hello");
sb.Append(" ");
sb.Append("World");
// 문자열 삽입
sb.Insert(5, ", ");
// 문자열 치환
sb.Replace("World", "C#");
// 문자열 삭제
sb.Remove(5, 2);
// 완성된 문자열 출력
string result = sb.ToString();
Console.WriteLine(result);
인터페이스
와 추상클래스
의 차이점을 잘 느낄 수 없어서 그 부분에서 많이 고전했다. (둘의 제일 큰 차이점은 다중 상속 가능/불가능일 텐데 그 차이가 내게 크게 와닿지 않아서인 듯)try-catch
를 배울 때에도 굳이 if
문을 두고 try-catch
를 쓰는 이유를 잘 모르겠어서 이것저것 살피느라 시간을 많이 잡아먹음.델리게이트
까지는 괜찮았으나 람다
식이 포함되기 시작하면서 코드 읽기가 점점 힘들어졌던 것 같다.