인터페이스와 열거형
다중 상속을 쓰지 않는 이유
- 다이아몬드 문제
다중 상속 시, 한 클래스에서 두 개 이상의 부모 클래스로부터 동일한 멤버를 상속한 경우.
동일한 멤버는 어떤 부모 클래스의 멤버를 써야할 지 모호해진다.
- 설계의 복잡성
관계가 복잡해진다.
- 이름 충돌과 충돌 해결의 어려움
어느 순간 이름이 중첩되어 충돌을 일으킨다.
- 설계의 일관성과 단순성을 유지하기 위함
C#은 단일 상속으로 설계의 단순성과 일관성을 유지
인터페이스를 쓰는 이유
- 코드의 재사용성
- 다중 상속 제공
- 유연한 설계
클래스와 인터페이스 간 느슨한 결합
인터페이스 Interface
- 현실 예시 : USB A 타입 포트
입구와 형태만 맞으면 다양한 곳에서 호환되어 쓸 수 있음
특징
- 클래스가 구현하는 멤버를 정의
- 클래스에 대한 제약 조건 명시
- 클래스에서 구현할 경우 인터페이스에 선언된 멤버들을 모두 정의해야함.
구현
Interface IMyInterface{
public void Method1();
}
class ClassA : IMyInterface{
public void Method1(){
}
}
Item item = new Item { Name = "Health Potion" };
다중 상속
Interface IUsable{
void Use();
}
Interface IDestoryable{
void Destory();
}
class Item : IUsable, IDestroyable
{
public void Use(){
}
public void Destory(){
}
}
인터페이스 vs 추상클래스
인터페이스
추상클래스
- 일부 동작의 구현 가능 + 추상 메서드를 포함할 수 있음.
- 단일 상속만 가능
열거형
사용 이유
- 가독성
연관된 상수들을 명명할 수 있음
- 자기 문서화
의미 있는 이름을 사용할 수 있음
- 스위치문과 호환성
스위치문과 쓸 때 깔끔하게 쓸 수 있다.
특징
- 서로 관련된 상수(int)들의 집합을 정의할 때 사용
enum MyEnum {
val1,
val2,
va13
}
enum MyEnum {
val1 = 10,
val2,
va13,
val4 = 100,
}
열거형 형변환
int intVal = (int)MyEnum.val1;
MyEnum eVal = (MyEnum)intVal;
스위치문 사용
switch(eVal){
case MyEnum.val1:
break;
case MyEnum.val2:
break;
...
}
예외 처리 및 값형 vs 참조형
예외
- 실행 중 예기치 못한, 예상하지 못한 상황
- 치명적인 오류의 원인 중 하나
예외 처리
- 예외 상황에 대비하여 안정적으로 유지하기 위한 작업
- 예외 처리로 안정성과 디버깅을 용이하게 함
구현
try
{
}
catch(ExceptionType1 ex)
{
}
catch(ExceptionType2 ex)
{
}
finally
{
}
catch
- 예외 발생 했을 때, 탐지 시 들어오는 구문
- 여러 예외에 대한 구문을 배치할 수 있음
- catch 블록은 순서대로 실행
- 예외에 해당하는 첫번째 catch 블록이 먼저 실행
- 여러 catch 블록을 써서 다양한 예외 타입 처리 가능
- 예외 객체
* catch문에서 예외 객체를 이용해서 예외에 대한 정보에 접근 가능
finally
- 예외가 실행했든 아니든 무조건 실행
- 예외 처리의 마지막 단계, 정리 단계
- 생략 가능
실행 시점
- 예외가 발생한 경우 : 예외 처리 과정 후 finally 블록 실행
- 예외가 발생하지 않은 경우 : 발생하지 않아도 finally 블록 실행
사용자 정의 예외
- 제공하는 예외 외에 사용자 자신만의 예외 클래스 작성 가능
- Exception 클래스 상속으로 사용 가능
- catch 문에 넣어서 사용
구현
try{
int result = 10 / 0;
}
catch(DivideByZeroException ex){
}
사용자 정의 예외 객체
class CustomException : Exception
{
public CustomException(string message) : base(message)
{
}
}
- initializer : 생성자가 실행되기 전에 처리할 것들을 명시
- base = 부모클래스
- base(message) : 부모 클래스 생성자를 먼저 호출하는 것
주의점
- 예외 처리 작성 시, 너무 많은 구문을 try에 넣으면 오히려 처리하기 힘들어짐.
- 예외가 일어날 것이라 우려되는 부분만 try에 넣기
값형 vs 참조형
값형 Value Type
- struct, 일반 변수(int, float, double, bool 등등)
- 변수값을 직접 저장
- 변수가 실제 데이터를 보유, 다른 변수에 할당하거나 전달할 때는 값이 복사
struct MyStruct{
public int val;
}
MyStruct s1;
s1.val = 10;
MyStruct s2 = s1;
s2.val = 20;
Console.WriteLine(s1.val);
참조형 Reference Type
- class, 배열, 인터페이스 등
- 참조형만 기억하면 편함
- 변수가 데이터에 대한 참조(메모리 주소) 를 저장
- 변수가 실제 데이터를 가리키는 참조(포인터) 를 갖고 있어, 다른 변수에 할당할 때 참조 복사가 이루어짐
class MyClass{
public int val;
}
MyClass obj1 = new MyClass();
obj1.val = 10;
MyClass obj2 = obj1;
obj2.val = 20;
Console.WriteLine(obj1.val);
값형 vs 참조형
변수 저장 방법
- 값형 : 실제 데이터를 변수에 저장
- 참조형 : 데이터에 대한 참조를 변수에 저장
변수 할당 및 복사
- 값형 : 변수 간 값 복사
- 참조형 : 변수 간 참조 복사
데이터 보유 방법
- 값형 : 변수가 독립적으로 데이터 보유
- 참조형 : 변수가 동일한 데이터를 참조
박싱, 언박싱
박싱 Boxing
- 값형 -> 참조형으로
- 값형 변수 값을 메모리의 힙 영역에 할당된 객체로 랩핑(Wrapping)
언박싱 Unboxing
- 참조형 -> 값형으로
- 박싱된 객체에서 값을 추출해서 값형 변수에 할당
박싱, 언박싱 주요 특징
- 값형 - 참조형 간 변환 작업. 작업 과정에서 메모리 소모가 크다.
(값형에 변경되는 게 아니라 값형은 그대로 있고 새로운 박싱 객체에 값을 넣어주는 것)
- 박싱된 객체는 힙 영역에 할당. 가비지 컬렉션의 대상이 될 수 있다.
메모리 관리에 유의해야한다.
- 박싱된 객체와 원래의 값형은 서로 독립적.
값을 수정해도 상호간 영향을 주지 않는다.
예시
- object 객체 :
.net에서 제공하는 공통 자료형의 일부.
모든 클래스의 직간접적인 상위 클래스.
int num1 = 10;
object obj = num1;
int num2 = (int)obj;
Console.WriteLine("num1: " + num1);
Console.WriteLine("num2: " + num2);
List<object> myList = new List<object>();
int intValue = 10;
myList.Add(intValue);
float floatValue = 3.14f;
myList.Add(floatValue);
int value1 = (int)myList[0];
float value2 = (float)myList[1];
tip
- 예외 처리 시, 가능한 구체적인 예외 클래스 사용해야 좋음.
코드가 더 안정적이고 예외 상황에 대한 처리가 더욱 명확해짐.
- 값형과 참조형, 박싱과 언박싱의 개념을 확실히 이해야한다.
C# 개발에서 매우 중요한 개념