C# 문법
1. 객체란 무엇인가요? 클래스와 어떤 연관이 있나요?
>> 객체란 세상에 존재하는 모든 것을 지칭하는 단어. ex) 사람, 자동차, 컴퓨터, 책 등등
모든 객체는 속성(데이터), 기능(함수)로 표현할 수 있다. 객체를 속성과 기능으로 표현하는 것을 추상화라고 한다.
클래스는 객체를 만들기 위한 청사진이라고 할 수 있다.
쉬운 예로는 자동차 설계도가 있다. 설계도는 자동차가 어떤 속성과 기능을 가져야 하는지를 결정해야 한다.
그리고 자동차의 속성과 기능 중 변경 가능한 것과 불가능한 것도 결정해야 한다.
설계도는 실체를 가지지 않지만, 공장에서 제작된 자동차는 실체가 있다.
자동차는 차량번호, 색상, 휠 사이즈 등등 다양한 속성과 주행, 정지 등등 다양한 기능을 가지게 된다.
여기서 자동차 설계도는 클래스, 객체는 자동차가 된다.
클래스를 자료형으로 식별자(변수명)를 선언하면 객체가 생성되어 Stack에 할당된다.
new 키워드와 해당 클래스 생성자를 호출하면 클래스의 인스턴스(instance)가 Heap에 할당되어 생성된다.
인스턴스를 생성해야 비로소 객체가 고유한 실체를 가지게 되고 저마다 Heap 메모리 공간을 차지하게 된다.
2. 생성자에 대해 간단하게 설명해주세요.
>> 객체의 인스턴스가 생성될 때 생성자가 호출된다.
생성자의 임무는 단 한가지, 해당 클래스의 인스턴스를 생성하는 것뿐이다.
클래스 선언 시 명시적으로 생성자를 구현하지 않아도 컴파일러에서 자동으로 만들어준다.
직접 구현해야 하는 경우도 있으며, 인스턴스 생성 시 필드를 원하는 값으로 초기화할 때 사용한다.
매개변수가 있는 생성자를 정의하면 기본 생성자는 제공되지 않는다.
3. 접근제한자란 무엇이며, 각각 어떤 차이가 있는지 비교해서 설명해주세요.
>> 객체 지향에서 캡슐화와 관련이 있다. 캡슐화란 관련 있는 데이터와 메서드를 객체로 만들고, 객체 밖에서 알아야 할 필요가 없는 내부 멤버를 숨기는 것을 의미한다.
접근 제한자 유형은 다음과 같다.
논리적 기준 : 클래스의 계층 구조에 따른 접근을 제한
- public : 내부 및 파생 클래스에서 접근 뿐만 아니라 외부에서도 접근을 허용한다.
- protected : 내부에서의 접근과 파생 클래스(자식 클래스)의 접근만 허용한다.
- private : 내부에서만 접근을 허용한다.
물리적 기준 : 어셈블리 단위로 접근을 제한
- internal : 동일한 어셈블리 내에서는 public에 준한 접근을 허용, 다른 어셈블리에서는 접근할 수 없다.
- internal protected : 동일한 어셈블리 내에서 정의된 클래스, 또는 다른 어셈블리면 파생 클래스만 접근 가능하다.
4. static 한정자에 대해 설명해주세요.
>> static의 사전적 의미는 정적, 움직이지 않는다는 뜻이다. C#에서 static은 필드나 메서드가 클래스의 인스턴스가 아닌 클래스 자체에 소속되도록 한다.
쉽게 말하면 메모리에서 고정시켜 움직이지 않는 것이다.
static은 Heap 메모리에 고정된 메모리를 사용하는 것이다. static 키워드를 사용하는 순간 메모리에 할당한다.
인스턴스는 여러 개 존재할 수 있지만 클래스는 단 하나 존재한다. 따라서 클래스 자체에 소속되는 정적 필드 또한 유일하게 존재하게 된다.
5. SOLID 원칙에 대해 설명해주세요.
>> SOLID 원칙이란 객체 지향 프로그래밍의 다섯 가지 기본 원칙을 나타내며, 각각의 원칙은 코드의 가독성, 확장성을 향상시키는 데 큰 도움이 된다.
SOLID : 각각의 앞 글자를 따서 명명
- Single Responsiblility Principle(SRP, 단일 책임 원칙) : 클래스, 모듈, 함수는 하나의 책임만 가져야 하며, 변경 이유도 하나여야 한다.
- Open/Closed Principle(OCP, 개방/폐쇄 원칙) : 소프트웨어 요소는 확장에는 열려있지만, 수정에는 닫혀있어야 한다. 즉, 새로운 기능을 추가할 때 기존 코드를 변경하지 않고도 가능해야 한다.
- LisKov Substitution Principle(LSP, 리스코프 치환 원칙) : 자식 클래스는 부모 클래스의 역할을 대체할 수 있어야 한다. 이는 상속구조에서 일관성을 유지하는 데에 중요하다.
- Interface Segregation Principle(ISP, 인터페이스 분리 원칙) : 특정 클라이언트를 위한 인터페이스는 범용 인터페이스보다 더 유용해야 한다. 사용하지 않는 메서드에 의존하지 않도록 인터페이스를 작게 분리하는 것이 좋다.
- Dependency Inversion Principle(DIP, 의존성 역전 원칙) : 고수준의 모듈은 저수준의 모듈에 의존해서는 안되며, 두 모듈 모두 추상화에 의존해야 한다. 이를 통해 시스템의 유연성과 재사용성을 높일 수 있다.
6. 객체지향 프로그래밍의 속성 중 하나인 다형성과 이를 활용한 설계의 장점에 대해 설명해주세요.
>> 다형성(Polymorphism)이란 객체가 다양한 형태를 가질 수 있음을 의미한다. 상속 받아 만들어진 파생 클래스는 다형성을 보여줄 수 있다.
부모 클래스의 메서드를 virtual로 선언하고, 자식 클래스의 메서드를 override 하면 자식 클래스는 상속 받은 메서드를 독자적으로 사용할 수 있다.
여러 자식 클래스에서 공통된 메서드를 활용하고 싶을 때 각 자식 클래스에서 따로 만들어주지 않아도 override 메서드를 사용할 수 있다.
7. override와 overload에 대해 설명해주세요.
>> overloading은 사전적 의미로는 과적하다 이다. 오버로딩은 하나의 메서드에 여러 가지로 구현하는 것을 말한다.
오버로딩을 하게 되면 하나의 메서드에 여러 개의 구현을 과적할 수 있다. 오버로딩은 같은 메서드 이름으로 매개변수의 개수 또는 타입을 다르게 정의할 수 있다.
동일한 역할을 하는 메서드의 경우 이름을 다르게 할 필요 없이 매개변수의 개수 또는 타입만 다르게 정의하면 된다.
오버로딩을 하면 메서드 이름을 새로 지을 필요가 없을 뿐만 아니라 코드를 일관성 있게 유지해준다. 컴파일러는 매개변수의 개수와 타입을 분석해서 어떤 메서드를 실행할 지 찾는다.
overriding은 사전적 의미로는 더 중요한, 최우선시 되는 이다. 오버라이딩은 기반 클래스에서 물려받은 메서드를 파생 클래스에서 재정의하는 것이다.
오버라이딩을 하게 되면 기반 클래스의 메서드보다 파생 클래스의 메서드가 더 우선시된다. 객체 지향 프로그래밍에서 다형성은 객체가 여러 형태를 가질 수 있다는 의미인데, 이 다형성을 구현하기 위해 오버라이딩을 사용한다.
8. 확장 메서드에 대해 설명하고 어떻게 활용했는지 알려주세요.
>> 확장 메서드는 기존 클래스의 기능을 확장하는 기법이다. 클래스의 외부에서 클래스의 메서드처럼 사용할 수 있는 새로운 메서드를 만들 수 있는 기능이다.
확장 메서드를 사용하는 방법은 확장 클래스를 선언할 때 static으로 선언하고, 확장 메서드를 만들 때도 static으로 선언을 해준다. 마지막으로 확장 메서드의 첫 번째 매개변수는 this 키워드를 사용한 후 확장하고자 하는 클래스 타입을 적어준다.
확장 메서드는 다음과 같은 때 활용할 수 있다.
- C# 혹은 외부에 정의되어있는 클래스들에 새로운 기능이 있는 메서드를 추가해야할 때
- 메서드를 새롭게 만드려하는 클래스가 이미 상속 등으로 인해 영향을 받는 클래스가 많을 때 확장 메서드로 만든 함수는 자식 클래스에서 것은 재정의할 수 없기 때문에
- 오히려 영향을 주고 싶은 클래스가 많을 때
C#에서는 LINQ에서 확장 메서드를 사용하고 있다.
기존 int에는 OrderBy라는 메서드가 없지만, LINQ를 네임스페이스에 추가하는 순간 int에도 OrderBy를 사용할 수 있게 된다.
9. 콜백이란 무엇인가요? 콜백을 사용해본 경험이 있을까요?
>> 콜백의 정의는 피호출자가 호출자를 다시 호출하는 것이다. 대표적으로 delegate에게 Function을 실행할 상황이 된다면 대신 해달라고 하는 것이다.
delegate를 사용해 본 경험이 있다.
10. 델리게이트(delegate; 대리자)란 무엇인가요?
>> delegate는 부탁받은 할 일을 전달해주는 역할을 하고, 그렇기에 먼저 할 일(메서드)를 넣어주는 것이 필요하다.
delegate 사용 이유는 다음과 같다.
- 코드를 매개변수나 반환 형식으로 사용하고 싶어서
- 콜백(CallBack)을 위해서
일반 함수 호출 방식은 프로그래밍이 유연하지 못하다.
한 함수를 실행할 때 조건에 따라 if 문을 이용하여 다른 함수도 작동하게 하는 것보다
delegate를 통해 함수가 실행되지 않았지만 이미 델리게이트 변수가 결과에 맞는 함수를 참조해서 실행할 때만 맞는 것을 불러오면 되는 것이다.
따라서 코드가 의존적이지 않고 유연하다.