25.10.01 개발일지(이것이 C#이다 chapter 8, 9)

2025. 11. 16. 18:09·10월 개발일지

 

25.10.01 개발일지

[이것이 C#이다 chapter 8, 9]

 

인터페이스, 추상 클래스와 프로퍼티

 

chapter 8

인터페이스

인터페이스는 클래스와 비슷하지만 메소드, 이벤트, 인덱서, 프로퍼티만 가질 수 있다는 차이가 있다.

인터페이스는 구현부가 없고, 접근 제한 한정자를 사용할 수 없고 인스턴스도 만들 수가 없다.

 

인터페이스를 상속받는 클래스의 인스턴스를 만드는 것은 가능하다.

파생클래스는 인터페이스에 선언된 모든 메소드를 구현해줘야 하며, 이 메소드들은 public 한정자로 수시해야 한다.

이와 같이 선언한 클래스는 다음과 같이 인스턴스화가 가능하다.

 

인터페이스는 인스턴스는 못 만들지만, 참조는 만들 수 있다.

-> 이 참조에 파생 클래스의 객체의 위치를 담는 것

-> 파생 클래스는 기반 클래스와 같은 형식으로 간주하기 때문에

인터페이스와 인터페이스로부터 상속받는 클래스의 관계에도 그대로 적용된다.

즉, 위에 그림에서 ConsoleLogger의 객체는 ILogger의 객체로 취급할 수 있다.

이렇게도 가능

 

IRunnable runnable = car as IRunnable;

runnable.Run();

 

IRunnable flyable = car as IFlyable;

flyable.Fly();

 

이 부분은 IRunnable, IRunnable 인터페이스의 객체를 만든 것이 아니라

FlyingCar라는 클래스를 객체로 만든 다음에 그 객체를 인터페이스 타입으로 참조하고 있는 것이다.

인터페이스를 상속하는 인터페이스

클래스만 인터페이스를 상속할 수 있는 것은 아니다.

구조체와 인터페이스도 인터페이스를 상속할 수 있다.

기존 인터페이스에 새로운 기능을 추가한 인터페이스를 만들고 싶을 때 인터페이스를 상속하는 인터페이스를 만들면 된다.

위 두가지의 경우에는 인터페이스를 수정할 수가 없어서 인터페이스를 상속하는 인터페이스를 이용해야 한다.

 

다중 상속

클래스는 죽음의 다이아몬드 문제 때문에 여러 클래스를 한꺼번에 상속할 수 없다.

다중 상속이 허용된다면 업캐스팅 문제가 발생한다.

위 그림처럼 다중상속이 되면 plane은 Run을 출력할지 Fly를 출력할지 모르게 된다.

그래서 C#은 다중상속을 허용하지 않는다.

 

하지만 인터페이스는 다중상속이 가능하다.

인터페이스는 내용이 아닌 외형을 물려주기 때문에 죽음의 다이아몬드 같은 문제도 발생하지 않는다.

 

기본 구현 메소드

인터페이스가 선언하는 메소드는 파생될 클래스가 무엇을 구현해야 할지를 정의하는 역할만 하면 됐기 때문에 메소드에 구현부가 없었다.

 

여기서 인터페이스에 새로운 메소드를 추가하면 파생된 클래스에서는 컴파일 에러가 발생한다.

 

이때 기본 구현 메소드를 사용할 수 있다.

 

인터페이스에 새로운 메소드를 추가할 때 기본적인 구현체를 갖도록 해서 기존에 있는 파생 클래스에서의 컴파일 에러를 막을 수 있다.

추상 클래스

추상 클래스는 인터페이스와 클래스의 사이다.

 

추상 클래스는 구현을 가질 수 있지만 클래스와 달리 인스턴스를 가질 수 없다.

인터페이스에서는 모든 메소드가 public으로 선언되는 반면,

추상 클래스는 클래스와 같이 한정자를 명시하지 않으면 모든 메소드가 private으로 선언된다.

 

추상 클래스는 추상 메소드를 가질 수 있다.

추상 메소드는 추상 클래스가 한편으로 인터페이스의 역할도 할 수 있게 해주는 장치이다.

-> 구현은 못하지만 파생 클래스에서 반드시 구현하도록 강제한다.

 

추상 클래스나 클래스는 그 안에서 선언되는 모든 필드, 메소드, 프로퍼티, 이벤트 모두 접근 한정자를 명시하지 않으면 private이다. 추상 메소드도 마찬가지.

 

하지만 약속 역할을 하는 추상 메소드가 자동으로 private가 되게 할 순 없다.

-> C# 컴파일러는 추상 메소드가 반드시 public, protected, internal, protected internal 한정자 중 하나로 수식될 것을 강요한다.

-> 클래스의 접근성 원칙도, 인터페이스의 접근성 원칙도 지켜질 수 있다.

업캐스팅

이 전체 코드 Main 부분에 Abstract Base obj = new Derived(); 코드의 뜻은

AbstractBase는 추상 클래스라서 직접 객체를 만들 수 없다.

-> AbstractBase obj = new AbstractBase(); // ❌ 불가능

대신 Derived 클래스는 AbstractBase를 상속받아 추상 메서드(AbstractMethodA)를 구현했기 때문에,

Derived는 실제로 객체를 만들 수 있다.

-> new Derived(); // ✅ 가능

 

그런데 obj의 타입을 AbstractBase로 선언했으니까,

obj는 **"추상 클래스 타입의 참조 변수"**이고 실제로 가리키는 객체는 Derived이다.

 

👉 이걸 **업캐스팅(upcasting)**이라고 부른다.

(자식 객체를 부모 타입으로 참조하는 것)

chapter 9

프로퍼티

클래스를 작성하다 보면 필드를 public으로 선언해버리고 싶은 충동이 들 때가 있다.

-> 은닉성이 떨어짐

프로퍼티를 이용하면 은닉성과 편의성 두마리의 토끼를 다 잡을 수 있다.

프로퍼티 선언 문법에서 get{}과 set{}을 일컬어 접근자라고 합니다.

get 접근자는 필드로부터 값을 읽어오고 set 접근자는 필드에 값을 할당합니다.

 

set 접근자를 구현하지 않으면 프로퍼티는 쓰기 불가, 읽기 전용이 된다.

자동 구현 프로퍼티

이 프로퍼티를

다음과 같이 자동 구현 프로퍼티로 대체할 수 있다.

 

여기서 한가지 더

자동 구현 프로퍼티를 선언함과 동시에 초기화를 수행할 수 있다.

 

프로퍼티와 생성자

객체를 생성할 때 프로퍼티를 이용하여 초기화할 수 있다.

초기화 전용 자동 구현 프로퍼티

프로퍼티를 읽기 전용으로 선언하는 방법은 set 접근자를 안 쓰고 get 접근자만 갖도록 하는 것이다.

 

읽기 전용 프로퍼티를 아주 간편하게 선언할 수 있도록 개선하였다.

init 접근자를 새로 도입했다.

init 접근자는 set 접근자처럼 외부에서 프로퍼티를 변경할 수 있지만,

객체 초기화할 때만 프로퍼티 변경이 가능하다는 점이 다르다.

이렇게 선언한 프로퍼티를 초기화 전용 자동 구현 프로퍼티라고 한다.

 

init 접근자는 초기화 이후에 발생하는 프로퍼티 수정을 허용하지 않으므로 컴파일 에러를 발생시킨다.

required 키워드

required 는 초기화가 필요한 프로퍼티를 실수로 초기화하지 않는 실수를 방지하게 해준다.

required로 한정하면 초기화를 누락한 채 생성자를 호출하면 컴파일 에러가 발생한다.

레코드 형식과 불변 객체

불변 객체는 내부상태(데이터)를 변경할 수 없는 객체를 말한다.

-> 상태를 변경할 수 없기 때문에 데이터 복사와 비교가 많이 이뤄진다.

-> 새로운 상태를 표현하기 위해 기존 상태를 복사한 뒤 일부를 수정해서 새로운 객체를 만들고,

상태를 확인하기 위해 객체 내용을 자주 비교한다.

 

레코드는 불변 객체에서 빈번하게 이뤄지는 이 두 가지 연산을 편리하게 수행할 수 있도록 도입된 형식이다.

 

불변 객체를 만드는 방법은

참조 형식은 클래스의 모든 필드를 readonly로 선언하면 되고,

값 형식은 readonly struct로 구조체를 선언하면 된다.

 

값 형식 객체는 다른 객체에 할당할 때 깊은 복사를 수행하여 모든 필드를 1:1로 비교하기 때문에 프로그래머가 직접 비교 코드를 작성하지 않아도 되서 편리하다. 하지만 필드가 많으면 많을수록 복사 비용이 커진다는 단점이 있다.

 

참조 형식은 메모리 주소만 복사하기 때문에 필드가 많아져도 복사비용을 걱정하지 않아도 되지만 참조 형식끼리 비교하려면 보통 object로부터 상속하는 Equals() 메소드를 오버라이딩하여 직접 비교 코드를 작성해야 하기 때문에 불편하다.

 

레코드 형식은 값 형식처럼 다룰 수 있는 불변 참조 형식으로,

참조 형식의 비용 효율과 값 형식의 편리함을 모두 제공한다.

 

레코드는 record 키워드와 초기화 전용 자동 구현 프로퍼티(init)를 함께 이용해서 선언한다.

주의할 점음 레코드에는 초기화 전용 자동 구현 프로퍼티뿐만 아니라 쓰기 가능한 프로퍼티와 필드도 자유롭게

선언해 넣을 수 있다는 사실이다.

with을 이용한 레코드 복사

C# 컴파일러는 레코드 형식을 위한 복사 생성자를 자동으로 작성한다.

이 복사 생성자는 protected로 선언되기 때문에 명시적으로 호출할 수는 없고 with식을 이용해야 한다.

with식이 없었다면 RTransaction 인스턴스를 새로 할당하면서 tr1의 모든 프로퍼티를 입력해줘야 했을 것이다.

-> with 식은 객체 상태(프로퍼티)가 다양할수록 유용하다.

 

레코드 객체 비교하기

클래스는 참조 형식이기 때문에 Equals()를 오버라이딩 하여 구현해야 비교가 가능하지만

레코드는 참조 형식이지만 값 형식처럼 Equals() 메소드를 구현하지 않아도 비교가 가능하다.

이 때 CTransaction은 클래스 객체기 때문에 Equals()를 구현하지 않으면 False가 출력된다.

무명 형식

C#에는 int, double, string 등 여러 형식이 있다.

무명 형식은 이름이 없는 형식이다.

 

형식의 이름은 인스턴스를 만들기 위해 필요하다.

-> 무명 형식은 형식의 선언과 동시에 인스턴스를 할당한다.

-> 인스턴스를 만들고 다시는 사용하지 않을 때 무명 형식이 요긴하다.

-> 무명 형식의 프로퍼티에 할당된 값은 변경불가능하다.

-> 인스턴스가 만들어지고 난 다음에는 읽기 전용이 된다.

 

 

'10월 개발일지' 카테고리의 다른 글

25.10.14 개발일지(C# 개인 프로젝트 게임 만들기)  (0) 2025.11.16
25.10.13 개발일지(이것이 C#이다 chapter 21, 22)  (0) 2025.11.16
25.10.06 개발일지(이것이 C#이다 chapter 20)  (0) 2025.11.16
25.10.05 개발일지(이것이 C#이다 chapter 13, 19)  (0) 2025.11.16
25.10.02 개발일지(이것이 C#이다 chapter 10, 11, 12)  (0) 2025.11.16
'10월 개발일지' 카테고리의 다른 글
  • 25.10.13 개발일지(이것이 C#이다 chapter 21, 22)
  • 25.10.06 개발일지(이것이 C#이다 chapter 20)
  • 25.10.05 개발일지(이것이 C#이다 chapter 13, 19)
  • 25.10.02 개발일지(이것이 C#이다 chapter 10, 11, 12)
dldmstj4378
dldmstj4378
dldmstj4378 님의 블로그 입니다.
  • dldmstj4378
    dldmstj4378 님의 블로그
    dldmstj4378
  • 전체
    오늘
    어제
    • 분류 전체보기 (136)
      • 비전 검사 (0)
      • 11월 개발일지 (6)
      • 10월 개발일지 (15)
      • 9월 개발일지 (26)
      • 8월 개발일지 (20)
      • 7월 개발일지 (26)
      • 6월 개발일지 (27)
      • 5월 개발일지 (16)
  • 블로그 메뉴

    • 홈
    • 태그
    • 방명록
  • 링크

  • 공지사항

  • 인기 글

  • 태그

  • 최근 댓글

  • 최근 글

  • hELLO· Designed By정상우.v4.10.3
dldmstj4378
25.10.01 개발일지(이것이 C#이다 chapter 8, 9)
상단으로

티스토리툴바