Table of Content
생성자와 소멸자
- 생성자를 하나라도 정의하면 C# 컴파일러는 기본 생성자를 제공하지 않음
- 소멸자는 오버로딩 불가능, 한정자 지정 불가능, 호출 불가능
- 소멸자는 구현하지 않는 것이 좋음. CLR(Common Language Runtime)이 알아서 객체를 수거해가기 때문.
class MyClass { private int id; private string name; /* 기본 생성자: 매개변수가 하나도 없음 */ public MyClass() { id = 1; name = "안녕요?ㅎ"; } /* 생성자를 구현하면 C# 컴파일러는 기본 생성자를 제공하지 않으므로 * 기본 생성자를 직접 구현해야 함 */ public MyClass(int id, string name) { /* this: 자신의 필드를 가리키는 키워드 */ this.id = id; this.name = name; } /* 소멸자 */ ~MyClass() { Console.WriteLine("ID: {0}, 넌 이미 죽어있다.", id); } /* 메소드 */ public void Print() { Console.WriteLine("ID: {0}, Name: {1}", id, name); } } class Program { public static void Main(string[] args) { MyClass c1 = new MyClass(); // 기본 생성자를 이용한 객체 생성 MyClass c2 = new MyClass(100, "김씨샵"); // 따로 구현한 생성자를 이용한 객체 생성 /* 클래스 인스턴스 메소드 호출 */ c1.Print(); c2.Print(); /* 프로그램이 끝나면 소멸자가 자동으로 호출됨 */ } } /* 출력 결과: * ID: 1, Name: 안녕요?ㅎ * ID: 100, Name: 김씨샵 * ID: 100, 넌 이미 죽어있다. * ID: 1, 넌 이미 죽어있다. */
클래스 상속
- 형식은 class 파생클래스 : 기반클래스
- 클래스 상속을 막으려면 sealed 한정자로 클래스를 구현하면 됨
/* Base: 기반 클래스 */ class Base { public void BaseMethod() { } } /* Derived: Base 클래스에 기반한 파생 클래스 */ class Derived : Base { /* base(): 기반 클래스의 생성자 */ public Derived() : base() { } public void DerivedMethod() { base.BaseMethod(); } } /* sealed 키워드: 클래스 상속 방지 */ sealed class ForbiddenClass { } /* 에러! 상속 불가 */ class DerivedClass : ForbiddenClass { }
접근 한정자
- public: 클래스 내/외부 모두 접근 가능
- protected: 파생 클래스에서 접근 가능
- private: 클래스 내부에서만 접근 가능 (기본값)
- 그 외: internal, protected internal
/* 기반 클래스 */ class Seoul { public int a; protected int b; private int c; } /* 파생 클래스 */ class Guro : Seoul { public Guro() { /* base: 기반 클래스의 필드를 가리키는 키워드 */ base.a = 1; // a는 public이므로 어디서나 접근가능 base.b = 2; // b는 protected이므로 파생클래스에서 접근 가능 base.c = 3; // 에러! c는 private이므로 Seoul에서만 접근 가능 } } class Program { public static void Main(string[] args) { Guro g = new Guro(); Console.WriteLine(g.a); // a는 public이므로 어디서나 접근 가능 Console.WriteLine(g.b); // 에러! b는 protected이므로 파생클래스 외에선 접근 불가능 Console.WriteLine(g.c); // 에러! c는 private이므로 Seoul에서만 접근 가능 } }
기반클래스 – 파생클래스 간 형식변환
- 파생클래스의 인스턴스는 기반클래스의 인스턴스로 사용 가능
- 기반클래스의 인스턴스는 파생클래스 형식으로 형변환 가능
- is: 객체형식을 확인하여 반환
- as: 형식변환 실패시 객체 참조를 null로 만듦
class Seoul { } class Guro : Seoul { } class Program { static void Main(string[] args) { Seoul s1 = new Seoul(); s1 = new Guro(); // 파생 클래스의 인스턴스는 기반 클래스의 인스턴스로 사용 가능 Seoul s2 = new Seoul(); Console.WriteLine("s2는 Seoul 형식? {0}", s2 is Seoul); // 출력: True Console.WriteLine("s2는 Guro 형식? {0}", s2 is Guro); // 출력: False Guro g1 = (Guro)s1; // 형변환 연산자: 기반 클래스의 인스턴스를 파생클래스 형식으로 강제 형변환 Guro g2 = s1 as Guro; // as 연산자: 형식변환 실패시 객체 참조를 null로 만듦 Console.WriteLine("g2는 Seoul 형식? {0}", g2 is Seoul); // 출력: True Console.WriteLine("g2는 Guro 형식? {0}", g2 is Guro); // 출력: True } }
다형성과 오버라이딩
- 다형성: 하위 형식 다형성의 준말. 파생 클래스를 통해 다형성을 실현.
- 오버라이딩
- 클래스 간의 상속 관계에서 메서드의 동작부를 재정의 하는 것. 반환 형식은 같게.
- 기반 클래스에서 상속받은 메소드를 파생 클래스에서 재정의
- 기반 클래스에서 오버라이딩할 메소드를 virtual 키워드로 한정
- 파생 클래스에서 오버라이딩할 메소드 구현 시 override 키워드 사용
- 기반 클래스에서 private로 선언한 메소드는 오버라이딩 불가능. 파생 클래스에서 보이지 않으므로.
class Seoul { /* 파생 클래스에서 오버라이딩할 메소드: virtual 키워드로 구현 */ public virtual void Clean() { Console.WriteLine("서울시청 청소"); } } class Guro : Seoul { /* 기반 클래스에서 상속받은 메소드 오버라이딩: override 키워드로 구현 */ public override void Clean() { Console.WriteLine("구로구청 청소"); } } class Program { static void Main(string[] args) { Seoul s = new Seoul(); s.Clean(); Guro g = new Guro(); g.Clean(); } }
메소드 숨기기
- 기반 클래스의 메소드를 숨기고 파생 클래스의 메소드를 새롭게 구현
- 파생 클래스에서 new 한정자로 메소드를 구현
class Seoul { public void Announce() { Console.WriteLine("서울시 공지사항"); } } class Guro : Seoul { /* new 키워드를 사용하여 기반 클래스의 메소드를 숨김 */ public new void Announce() { Console.WriteLine("구로구 공지사항"); } } class Program { static void Main(string[] args) { /* Guro의 Announce() 호출. Seoul의 Announce()는 숨겨짐 */ Guro g = new Guro(); g.Announce(); /* Seoul의 Announce() 호출. Seoul의 Announce()는 숨겨지지 않음 */ Seoul s = new Seoul(); s.Announce(); } }
오버라이딩 봉인
- 오버라이딩한 메소드를 sealed로 한정하면 이후 오버라이딩 불가
- 잘못된 오버라이딩으로 인해 오류발생 가능성이 있다면 상속을 사전에 막는 것이 좋음
class Seoul { /* 파생 클래스에서 오버라이딩할 메소드: virtual 키워드로 구현 */ public virtual void Clean() { Console.WriteLine("서울시청 청소"); } } class Guro : Seoul { /* 오버라이딩한 메소드를 sealed로 한정하면 이후 오버라이딩 불가 */ public sealed override void Clean() { Console.WriteLine("구로구청 청소"); } } class Sindorim : Guro { /* 에러! 오버라이딩 불가능 */ public override void Clean() { Console.WriteLine("신도림동 청소"); } }
중첩 클래스(Nested Class)
- 클래스 안에 선언된 클래스
- 중첩 클래스(Nested Class)는 소속된 클래스(Outer Class) private 멤버에도 접근 가능
- → Outer Class에서 Nested Class 멤버엔 접근 불가
- 왜 쓰는가?
- 클래스 외부에 공개하고 싶지 않은 형식을 만들 때
- 현재 클래스의 일부분처럼 표현할 수 있는 클래스를 만들 때
/* 소속된 클래스(Outer Class) */ class OuterClass { private int outerValue; public void ControlNested() { NestedClass nc = new NestedClass(); nc.nestedValue = 1; // 에러 O: Outer -> Nested private 멤버 접근 불가 } /* 중첩 클래스(Nested Class) */ class NestedClass { private int nestedValue; public void ControlOuter() { OuterClass oc = new OuterClass(); oc.outerValue = 1; // 에러 X: Nested -> Outer private 멤버 접근 가능 } } }
분할 클래스(Partial Class)
- 클래스의 구현이 길어질 경우 여러 개의 파일로 구현 가능
- partial 키워드 사용
partial class Seoul { public void Clean() { Console.WriteLine("서울시청 청소"); } } partial class Seoul { public void Announce() { Console.WriteLine("서울시청 공지"); } }
확장 메소드(Extension Method)
- 클래스를 인스턴스화하지 않고도 해당 클래스(형식)에서 바로 사용할 수 있는 메소드
- 구현 조건
- 구현하려는 클래스를 static으로 한정
- 구현하려는 메소드를 static으로 한정
- 구현하려는 메소드의 첫번째 매개변수는 this 키워드를 사용하며,, 확장하고자 하는 클래스(형식)의 인스턴스를 지정
/* 확장 메소드를 포함하는 클래스: static으로 한정 */ public static class MyExtension { /* 확장 메소드 조건 * 1. static으로 한정 * 2. 첫번째 매개변수는 this 키워드 사용, * 확장하고자 하는 클래스(형식)의 인스턴스 지정 * 3. 두 번째 매개변수부터는 입력받을 매개변수 지정 */ public static int Factorial(this int i) { if (i == 0 || i == 1) return 1; else return i * Factorial(i - 1); } } class Program { static void Main(string[] args) { Console.WriteLine(15.Factorial()); // int 형식에 대한 확장 메소드 호출 } }
구조체(Structure)
- 데이터를 담기위한 구조로 사용되므로 주로 public으로 구현
- 클래스와의 차이점
- 클래스는 참조 형식, 구조체는 값 형식
- 클래스는 인스턴스를 new 연산자로 생성, 구조체는 선언만으로 생성
- 클래스는 매개변수 없는 생성자 선언 가능, 구조체는 매개변수 없는 생성자 선언 불가
- 클래스는 상속 가능, 구조체는 System.Object 형식을 상속하는 System.ValueType으로부터 직접 상속받음
/* 구조체 */ struct Point2D { public int x; public int y; /* 매개변수 없는 구조체의 생성자는 선언 불가 */ public Point2D(int x, int y) { this.x = x; this.y = y; } /* System.Object 형식의 ToString() 메소드 오버라이딩 */ public override string ToString() { return string.Format("({0}, {1})", x, y); } } class Program { static void Main(string[] args) { Point2D p1; // 구조체는 선언만으로도 인스턴스 생성 p1.x = 1; p1.y = 2; Console.WriteLine(p1.ToString()); Point2D p2 = new Point2D(10, 20); Point2D p3 = p2; // 구조체의 인스턴스를 다른 구조체 인스턴스에 할당하면 깊은 복사가 이루어짐 Console.WriteLine(p2.ToString()); Console.WriteLine(p3.ToString()); } }
인터페이스(Interface)
- 구현부가 없는 메소드, 이벤트, 인덱서, 프로퍼티의 집합
- 접근제한 한정자 사용 불가 (기본: public)
- 인터페이스를 상속받는 클래스는 인터페이스의 모든 메소드(및 프로퍼티)를 구현해야 함
- 관행적인 명명 규칙: 인터페이스 이름 앞에 ‘I’를 붙임
- 인터페이스의 인스턴스는 생성 불가능하지만 참조는 만들 수 있음
/* 인터페이스 */ interface ILogger { void WriteLog(string message); } /* 인터페이스를 상속받는 클래스 */ class ConosoleLogger : ILogger { /* 인터페이스로부터 상속받은 메소드 구현 */ public void WriteLog(string message) { Console.WriteLine("Log: {0}", message); } }
인터페이스를 상속받는 인터페이스
interface ILogger { void WriteLog(string message); } /* ILogger 인터페이스를 상속받은 인터페이스 */ interface IFormattableLogger : ILogger { /* 이 인터페이스는 2개의 WriteLog() 메소드를 가짐 */ void WriteLog(string format, params Object[] args); }
여러 개의 인터페이스를 상속받는 클래스
interface IDog { void Bark(string s); } interface ICat { void Meow(string s); } /* 여러 개의 인터페이스를 상속받는 클래스 */ class Mammal : IDog, ICat { public void Bark(string s) { Console.WriteLine(s); } public void Meow(string s) { Console.WriteLine(s); } }
추상 클래스(Abstract Class)
- abstract 한정자로 선언
- 특징
- 인터페이스적 특징: 인스턴스를 가질 수 없음, 추상 메소드를 지닐 수 있음
- 클래스적 특징: 메소드 구현 가능
- 필드, 메소드, 프로퍼티, 이벤트 기본 접근한정자는 private
- 그러나 추상 메소드는 선언 시 private 외의 접근 한정자를 사용해야 함
- 추상클래스를 사용하면 다른 프로그래머가 파생클래스를 구현할 때 모든 추상 메소드를 구현해야 하는 사실을 잊어도 컴파일러가 그 사실을 상기시켜줌 → 코드에 대한 매뉴얼 작성 부담이 줄어듦
/* 추상 메소드 */ abstract class MyAbstractClass { private void Method() { } // 메소드 public abstract void Method2(); // 추상 메소드 } /* 추상 메소드를 상속받는 클래스 */ class MyClass : MyAbstractClass { public override void Method2() { } // override 키워드를 사용하여 추상 메소드 구현 }