일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
1 | ||||||
2 | 3 | 4 | 5 | 6 | 7 | 8 |
9 | 10 | 11 | 12 | 13 | 14 | 15 |
16 | 17 | 18 | 19 | 20 | 21 | 22 |
23 | 24 | 25 | 26 | 27 | 28 |
- 코테
- 2468 c++
- Beakjoon
- 백준 1103번
- C#
- 백준 17070번 c++
- 백준 c++ 2870번
- dfs
- 2870번 c++
- Unity
- 백준 c++ 2468번
- 백준 2870번
- 유니티
- 17070번
- 2870번 수학숙제 c++
- 백준 17070번
- 백준 1103번 게임
- Lv.3
- 백준 1103번 c++
- Lv2
- 수학숙제
- 코딩테스트
- 2870번
- 백준
- 오브젝트 풀링
- 플레이어 이동
- 프로그래머스
- 2870번 수학숙제
- c++
- Algorithm
- Today
- Total
주녘공부일지
[C#] 인터페이스 ( interface ) 본문
1. 인터페이스의 상속
- 인터페이스는 다중 상속이 가능함
+ 다중 상속으로 인해 인터페이스의 멤버이름이 충돌할 수 있는데, 이를 해소하는 방법 중 하나는 멤버를 명시적으로 구현하는 것임 ( 후술 - 4번 )
interface I1 { ... }
interface I2 { ... }
public class A : I1, I2 { ... } // 다중 상속
- 인터페이스가 인터페이스를 상속받는 것도 가능함
ex) IUndoable의 모든 멤버를 상속받는 IRedoable 인터페이스
public interface IUndoable { void Undo(); }
public interface IRedoable : IUndoable { void Redo(); }
즉, IRedoable 인터페이스를 상속받는 클래스는 IUndoable의 멤버들도 반드시 구현해야 함 ( 구현 강제 - 형식 제공 )
2. 인터페이스의 멤버
인터페이스의 멤버는 암묵적으로 공용(public)이고 추상(abstract)임
- public 외의 접근 지정자를 따로 지정할 수 없음
- 추상 멤버만 가질 수 있음 ( 메서드, 속성, 이벤트, 인덱서 )
즉, 인터페이스를 상속 받은 클래스는 인터페이스의 모든 멤버에 대해 각각 public 구현을 강제함
// using System.Collections로 사용할 수 있는 인터페이스
namespace System.Collections
{
public interface IEnumerator
{
object Current { get; }
bool MoveNext();
void Reset();
}
}
public class Countdown : IEnumerator
{
int count = 11;
// 인터페이스의 멤버들은 public으로 선언해야 함
public object Current => count;
public bool MoveNext() => count-- > 0;
public void Reset() { throw new NotSupportedException(); }
}
3. 인터페이스의 캐스팅
인터페이스를 구현하는 클래스의 객체는 상속하는 인터페이스로 캐스팅 할 수 있음
ex) 위 예제의 Countdown 클래스를 IEnumerator로 캐스팅 // 외부에서도 공용으로 접근 가능
static void Main()
{
// 인터페이스로 캐스팅
IEnumerator e = new Countdown();
while (e.MoveNext())
Console.Write($"{e.Current} "); // 결과 : 10 9 8 7 6 5 4 3 2 1 0
}
구조체를 인터페이스로 캐스팅할 경우 박싱이 발생하기 때문에 지양해야 함
interface I { void Foo(); }
struct S = I { public void Foo(){...} }
static void Main()
{
S s = new S();
s.Foo(); // 박싱 없음
I i = s; // 구조체 -> 인터페이스로의 캐스팅에서 박싱 발생
i.Foo();
}
4. 인터페이스의 명시적 구현 & 암묵적 구현
암묵적 구현
- public으로 선언되어 외부에서 객체를 통해 호출 가능
- virtual 키워드를 통해 재정의 가능
명시적 구현
- private으로 선언되어 외부에서 호출하기 위해서는 객체를 인터페이스 타입으로 업캐스팅 해야 함
- virtual 키워드를 통해 재정의 불가능
ex) 인터페이스의 멤버이름이 충돌할 경우 해소하는 방법 - 명시적 구현
// 멤버 이름이 동일한 인터페이스 I1, I2
interface I1 { void Foo(); }
interface I2 { void Foo(); }
public class Widget : I1, I2
{
// 암묵적 구현
public void Foo() => Console.WriteLine("I1.Foo()");
// 명시적 구현
void I2.Foo() => Console.WriteLine("I2.Foo()");
}
static void Main()
{
Widget w = new Widget();
I2 i2 = w;
w.Foo(); // I1.Foo()
i2.Foo(); // I2.Foo()
((I1)w).Foo(); // I1.Foo()
((I2)w).Foo(); // I2.Foo()
}
5. 인터페이스의 가상 구현 (재정의)
virtual 키워드를 사용해 메서드를 재정의 할 수 있음
- 재정의 시에 인터페이스 캐스팅을 통해서도 재정의된 메서드로 호출됨
- base 키워드를 사용하여 확장할 수 있음
단, 명시적 구현된 메서드는 virtual 키워드를 사용할 수 없음
public interface IUndoable { void Undo(); }
public class TextBox : IUndoable
{
// 가상함수로 선언
public virtual void Undo() => Console.WriteLine("TextBox.Undo");
}
public class RichTextBox : TextBox
{
// override로 재정의 (확장 가능 - base 키워드)
public override void Undo()
{
// base.Undo(); // base 키워드 사용 가능
Console.WriteLine("RichTextBox.Undo");
}
}
static void Main()
{
RichTextBox r = new RichTextBox();
r.Undo(); // RichTextBox.Undo
((TextBox)r).Undo(); // RichTextBox.Undo
((IUndoable)r).Undo(); // RichTextBox.Undo
}
6. 인터페이스의 재구현
기존 멤버 구현을 '하이재킹' 하는 것으로, 인터페이스를 상속받아 메서드를 재구현
( 단, 이는 대체로 일관성이 깨지는 것을 뜻하므로 바람직하진 않음 )
- 가상 함수, 명시적 구현, 암묵적 구현, 등의 영향을 받지 않음
- 명시적 구현되어 있는 경우에 가장 효과적
ex) TextBox에 명시적 구현된 Undo() 메서드를 재정의하기 위해 IUndoable의 Undo() 메서드를 재구현하여 호출
public interface IUndoable { void Undo(); }
public class TextBox : IUndoable
{
// 명시적 구현된 메서드
void IUndoable.Undo() = Console.WriteLine("TextBox.Undo");
}
public class RichTextBox : TextBox, IUndoable // IUndoable
{
// IUndoable의 Undo() 메서드를 재구현하여 재정의
public void Undo() => Console.WriteLine("RichTextBox.Undo");
}
static void Main()
{
RichTextBox r = new RichTextBox();
r.Undo(); // RichTextBox.Undo
((IUndoable)r).Undo(); // RichTextBox.Undo
}
만약 위 예제의 TextBox 클래스가 Undo 메서드를 명시적으로 구현한 것이 아닌 암묵적 구현했다고 가정한다면, 아래 코드처럼 형식 체계를 깨뜨리는 코드가 가능함
- 즉, 기반 클래스가 아닌 인터페이스를 통해 호출할 때만 효과적임
((TextBox)r).Undo(); // TextBox.Undo
이 외에도 다른 문제점들이 존재
1) 파생 클래스에서 기반 클래스의 메서드를 호출할 수 없음
2) 기반 클래스 작성자는 자신의 메서드가 재구현될 것임을 예상하지 못할 수 있으므로 재구현 시 문제가 발생할 가능성이 높아짐
즉, 멤버의 재구현은 명시적으로 구현된 인터페이스 멤버를 재정의하는 수단으로 사용하는 것이 가장 적합하며 파생을 염두에 두지 않고 기반 클래스를 파생하려 할 때의 마지막 수단으로 사용하는 것이 효과적이지만 가능한 재구현이 필요하지 않은 형태로 기반 클래스를 설계하는 것이 바람직함
인터페이스 재구현의 대안
- 인터페이스의 어떤 멤버를 암묵적으로 구현할 때에는 그 멤버를 virtual로 선언 (적합한 경우)
- 파생 클래스가 그 멤버를 임의의 논리로 재정의할 수도 있다고 예상할 때에는 아래와 같은 패턴을 적용
public class TextBox : IUndoable
{
void IUndoable.Undo() => Undo(); // 아래의 메서드를 호출
protected virtual void Undo() => Console.WriteLine("TextBox.Undo");
}
public class RichTextBox : TextBox
{
protected override void Undo() => Console.WriteLine("RichTextBox.Undo");
}
+ 더 이상의 파생이 없다고 예상되는 경우, sealed 한정자로 지정해 인터페이스의 재정의를 금지하는 것도 좋음
번외) 클래스와 인터페이스
클래스 : 구현을 공유하는 것이 자연스러운 형식에 사용
인터페이스 : 구현이 각자 독립적인 형식에 사용
+ 인터페이스의 이름은 대문자 I로 시작하는 것이 관례
참고도서) C# 6.0 완벽가이드
'Programming > Definition, Etc' 카테고리의 다른 글
[C#] 제네릭 (Generic) (0) | 2023.10.27 |
---|---|
[C#] 열거형 (enum type) (1) | 2023.10.15 |
[C#] Object 형식과 박싱&언박싱 (0) | 2023.10.11 |
[C#] 스택 메모리 & 힙 메모리 (0) | 2023.10.04 |
[C#] 값 형식과 참조 형식 ( + 제네릭 형식, 포인터 형식 ) (0) | 2023.09.29 |