C# 23년 ~ 24년 면접 질문
▼ Question 다음 코드의 출력값은 무엇인가요?
using System;
class Program
{
static void Main(string[] args)
{
string a = "잘";
string b = a;
b = "모르겠습니다";
Console.WriteLine(a + b);
}
}
▼ Answer
출력 값 : 잘모르겠습니다
풀이 : string a = "잘"
- a 에는 "잘"이 들어간다.
string b = a;
- 이 시점의 b 도 "잘"이 됀다.
b = "모르겠습니다";
- 이제 b 는 "모르겠습니다" 로 바뀐다.
- 하지만 a 는 그대로 "잘" 이다.
Console.WriteLine( a + b )
- a 와 b 를 이어 붙여서 출력하므로
- "잘" + "모르겠습니다"
- 결과는 잘모르겠습니다
헷갈리기 쉬운 포인트
b = a 했다고 해서 이후에 b 를 바꾸면 a 도 같이 바뀌는게 아니다.
String 의 특성에 대하여 설명해주세요.
- 참조형이지만 Immutable ( 불변성 ) 을 가지고 있다는 점을 포함해서 답하면 좋다.
불변, Immutable 에 대하여 설명해주세요.
- String 변수에 다른 값을 대입하면 생기는 일 ( 새로 불변 객체를 만들고 참조 등 )
이런 과정을 설명하는 것도 좋고, 사전적인 의미를 설명해도 좋다.
정수형은 + 기호로 덧셈이 일어나고, 문자열은 합쳐진 문자열이 나오는 이유에 대해 설명해주세요.
- 연산자 ( + 혹은 == 등 ) 오버로딩을 아는지 묻는 문제이다.
- 이어서 단순히 string 들을 더하는 식으로 제작하면 어떤 최적화 문제가 있을 수 있는지 언급해도 좋다.
StringBuilder 에 대하여 설명해주세요.
- 무엇인지, 왜 사용하면 좋은지, string 대신 사용해서 얻는 이점을 말하면 좋다.
▼ Question 다음 코드의 결과를 예상하고, 원인을 서술하세요.
using System;
namespace InterviewQuestion
{
class Program
{
static void Main(string[] args)
{
int num = 1;
for (int i = 0; i < 35; i++)
{
num *= 2;
Console.WriteLine(num);
}
}
}
}
▼ Answer
▼ 출력 값 :
2
4
8
16
32
64
128
256
512
1024
2048
4096
8192
16384
32768
65536
131072
262144
524288
1048576
2097152
4194304
8388608
16777216
33554432
67108864
134217728
268435456
536870912
1073741824
-2147483648
0
0
0
0
▼ 풀이 :
1. 반복문이 하는 일
- num 은 처음에 1
- 반복할 때마다 num *= 2 이므로 2배씩 커진다.
- 출력 값은 기본적으로 2, 4, 8, 16 ... 형태로 증가한다.
2. 갑자기 음수가 되는 이유
- int 는 C# 에서 4바이트 ( 32비트 ) 정수형이고, 표현할 수 있는 범위는 다음과 같다.
- 최소 값 : -2147483648
- 최대 값 : 2147483648
그런데 값이 계속 2배가 되다가
- 1073741824 다음에 한 번 더 2배 하면
- 수학적으로는 2147483648
이 되어야 한다.
하지만 이 값은 int 의 최대 값 2147483647 을 넘어서기 때문에 오버플로우가 발생
그래서 실제 출력은 2147483648 이 아니라 - 2147483648 이 나온다.
3. 그 다음부터 0 이 되는 이유
이후 값은 이미 - 2147483648 인 상태이다.
여기서 다시 2배를 하게 되면 - 2147483648 * 2 이 된다.
32 비트 정수 범위를 다시 넘으면서 비트가 밀려나고 결과는 0 이 된다.
이후 0 에 2를 계속 곱해도 결과는 0 이므로 0 만 출력하게 된다.
4. 핵심 정리
이 코드의 핵심 원인
- int 의 범위를 초과했기 때문
- 정소 오버플로우 발생
출제자의 의도
- 자료형이 런타임 도중 초과되는 숫자를 담을 경우 어떤 일이 발생하는지?
int, char 등 담을 수 있는 숫자 범위에 대해 설명해주세요.
- 대략적인 범위를 답해도 좋고, 몇 바이트짜리인지 답하는 것도 좋다.
Uint 와 int 의 차이에 대하여 설명해주세요.
- 사전적인 설명과 더불어 Unsigned 의 뜻과, Sign 의 뜻은 무엇인지 언급 해주는 것이 좋다.
- 정수의 표현 방식을 설명할 수 있어야 한다
비트시프트에 대하여 설명해주세요.
- 단어 설명도 좋지만 숫자의 2배 또는 반토막을 구현할 때 비트시프트를 사용해서 어떻게, 왜 최적화가 가능했는지 설명하면 좋다.
▼ Question 다음 코드의 출력 값 및 이유를 서술하세요.
using System;
namespace ConsoleApp2
{
internal class Program
{
static void Main(string[] args)
{
double PI = 3.14f;
if (PI * 2 == 6.28)
{
Console.WriteLine("PI의 2배는 6.28");
}
}
}
}
▼ Answer
출력 결과 :
아무것도 출력되지 않을 가능성이 높다.
풀이 :
핵심은 실수 비교이다.
double PI = 3.14f;
여기서 3.14f 는 float 타입 리터널이다.
먼저 float 값으로 저장된 뒤, 그 값을 double 변수 PI 에 넣었다.
문제는 float 와 double 은 실수를 정확히 저장하지 못할 수 있다는 점이다.
컴퓨터는 실수를 2진수로 저장하는데, 3.14 같은 값은 내부적으로 정확히 떨어지지 않는 경우가 많다.
그래서 실제 내부 값은 우리가 눈으로 보는 3.14 , 6.28 과 미세하게 다르다.
PI * 2 == 6.28
겉보기에는 같아 보여도, 실제로는
- PI * 2 의 내부 값
- 6.28 의 내부 값
이 정확히 일치하지 않을 수 있어서 false 가 된다.
그래서 if 문 안으로 들어가지 못하고, 출력이 되지 않을 수 있다.
- 3.14f 를 float 에 저장
- 그 값을 double 에 대입
- PI * 2 계산
- 6.28 과 정확히 같은지 비교
이 과정에서 부동소수점 오차가 생길 수 있다.
이 문제의 핵심
- float / double 같은 실수형은 오차가 생길 수 있다
- 그래서 실수끼리 == 비교는 주의해야 한다
출제자의 의도
- 부동소수점 표현 방식을 알고 있는지?
부동소수점에 대하여 설명해주세요.
- 지수부, 가수부로 외우는 것도 좋지만, 컴퓨터는 표현을 할 때 부동소수점 방식을 왜 사용하는지 이유도 답하면 좋다.
고정소수점 방식과 부동소수점 차이에 대하여 설명해주세요.
- 차이점 설명과 더불어, 고정소수점을 사용하면 좋은 상황을 추가로 설명하면 좋다.
▼ Question 다음 코드를 보고 출력과 그 이유를 서술하세요.
using System;
public class MyClass
{
public delegate void MyDel();
public MyDel OnRunning;
public MyClass() // 생성자
{
int a = 0; // 지역 변수
OnRunning = () =>
{
a++;
Console.WriteLine(a);
};
}
}
class Program
{
static void Main(string[] args)
{
MyClass mc = new MyClass();
mc.OnRunning?.Invoke();
mc.OnRunning?.Invoke();
mc.OnRunning?.Invoke();
}
}
▼ Answer
출력 값 :
1
2
3
풀이 :
이유는 a 가 생성자 안의 지역 변수인데, 람다식이 그 변수를 캡처하고 있다.
int a = 0;
OnRunning = () =>
{
a++;
Console.WriteLine(a);
};
여기서 OnRunning 에 들어간 람다는 단순히 그 순간의 a 값을 복사해 두는 게 아니라,
변수 a 자체를 참조해서 계속 사용한다.
그래서 호출할 때마다
- 첫 번째 호출 : a = 1
- 두 번째 호출 : a = 2
- 세 번째 호출 : a = 3
이전 값이 유지되면서 증가한다.
핵심 개념은 클로저 ( Closure ) 이다.
- a 는 원래 생성자 안에서만 존재하는 지역 변수처럼 보이지만
람다가 a 를 사용하고 있기 때문에 메서드가 끝난 뒤에도 a 가 살아 있는 것처럼 동작한다.
즉, 이 코드는
- 지역 변수를 람다가 캡처
- 그 상태가 유지되면서
- Invoke() 할 때마다 같은 a 를 계속 증가시키는 구조이다.
▼ Question 다음 코드를 보고 출력과 그 이유를 서술하세요.
using System;
using System.Collections.Generic;
class Program
{
static void Main(string[] args)
{
List<Action> actions = new List<Action>();
for (int i = 0; i < 3; i++)
{
actions.Add(() => Console.WriteLine(i));
}
foreach (var action in actions)
{
action();
}
}
}
▼ Answer
출력 값 :
3
3
3
풀이 :
이유는 람다가 i 값을 그때그때 복사해서 저장한 것이 아니라 i 변수 자체를 캡처하기 때문이다.
for (int i = 0; i < 3; i++)
{
actions.Add(() => Console.WriteLine(i));
}
겉으로 보기에는
- 첫 번째는 0
- 두 번째는 1
- 세 번째는 2
가 저장될 것처럼 보인다.
하지만 실제로는
- 0, 1, 2 라는 값이 각각 저장되는 게 아니라
- 같은 i 변수 하나를 참조하는 람다 3개가 리스트에 들어간다.
for 문이 모두 끝나면 i 는 3 이 됀다.
그래서 나중에 foreach 에서 각 액션을 실행할 때는 모두 같은 i 를 보고 있고, 그 값이 이미 3 이므로 3만 출력하게 됀다.
핵심 개념
이 문제의 핵심
- 람다식의 클로저 ( Closure )
- 반복문의 변수 캡처
람다는 변수의 값을 저장하는 게 아니라, 변수 자체를 캡처할 수 있다.
출제자의 의도
- C# 언어의 Delegate 의 깊숙한 이해도가 있는지?
캡처와 클로저를 설명해주세요.
- 캡처와 클로저에 대한 기본 설명 및, ( 만약 해당이 된다면 ) 지역변수의 변화된 생명주기까지 같이 언급하면 좋다.
사용 사례에 대해 알려주세요.
- 특정 디자인 패턴을 사용할 때 어떤 목적으로 사용했는지, 클로저 관련하여 오작동 및 그로 인한 사례를 언급해도 좋다.
▼ Question 다음 코드를 보고 출력과 그 이유를 서술하세요.
using System;
class Program
{
static void Main(string[] args)
{
Parent pc = new Child(10, 20);
}
}
public class Parent
{
protected Parent(int value)
{
Console.WriteLine("부모생성자" + value);
}
}
public class Child : Parent
{
public Child(int firVal, int secVal) : base(secVal)
{
Console.WriteLine("자식생성자" + firVal);
}
}
▼ Answer
출력 값 :
부모생성자20
자식생성자10
풀이 :
Parent pc = new Child(10, 20);
1. main 에서 Child 객체를 생성한다.
2. 생성자는 부모가 먼저 실행됀다.
Child 는 Parent 를 상속하고 있다.
public Child(int firVal, int secVal) : base(secVal)
여기서 : base(secVal) 는 부모 생성자에 secVal 값을 전달하겠다는 뜻이다.
지금 new Child(10, 20) 이므로
- firVal = 10
- secVal = 20
그래서 먼저 부모 생성자가 Parent(int value) 형태로 호출되고, value 에는 20 이 들어간다.
따라서 첫 출력은 부모생성자20 이 됀다.
3. 그 다음 자식 생성자 실행
부모 생성자 실행이 끝난 뒤에 자식 생성자 본문이 실행됀다.
Console.WriteLine("자식생성자" + firVal);
여기서 firVal 은 10 이므로 자식생성자10 이 출력됀다.
핵심 정리
- 객체 생성 시 부모 생성자가 먼저 호출된다
- : base(secVal) 로 인해 부모 생성자에는 secVal, 즉 20이 전달된다
출제자의 의도
- 상속을 잘 이해하고 있는지?
- 생성자 호출 순서를 아는지?
- Base 를 아는지?
다형성에 대하여 설명해주세요.
- 사전적인 뜻을 설명해도 좋고, 더 나아가서 필요에 따라 부모형, interface 또는 추상형으로 형을 바꿔 활용한 예시를 언급해도 좋다.
▼ Question 다음 코드를 보고, 출력 값과 이유를 서술하세요.
using System;
class Car
{
public void doWork()
{
Console.WriteLine("단순주행");
}
}
class Tractor : Car
{
public new void doWork()
{
Console.WriteLine("밭갈기");
}
}
internal class Program
{
static void Main(string[] args)
{
Car car1 = new Car();
car1.doWork();
Car tractor1 = new Tractor();
tractor1.doWork();
}
}
▼ Answer
출력 값 :
단순주행
단순주행
풀이 :
1. 첫 번째 호출
Car car1 = new Car();
car1.doWork();
이건 Car 객체를 만들고 Car 의 doWork() 를 호출하므로
단순주행
이 출력됀다.
2. 두 번째 호출
Car tractor1 = new Tractor();
tractor1.doWork();
여기서 헷갈리기 쉽다.
- 실제 객체는 Tractor
- 하지만 변수 타입은 Car
그리고 Tractor 의 메서드는 이렇다.
public new void doWork()
여기서 new 는 오버라이드 ( override ) 가 아니라 부모의 메서드를 숨김 ( hiding ) 처리한 것이다.
- Car 타입으로 보면 Car.doWork()
- Tractor 타입으로 보면 Tractor.doWork()
지금은 변수 타입이 Car 이므로 Car의 doWork() 가 호출한다.
핵심 개념
new 와 override 의 차이
new
- 부모 메서드를 재정의하는 게 아니라 숨김
- 호출되는 메서드는 변수 타입 기준으로 결정
override
- 부모의 virtual 메서드를 재정의
- 호출되는 메서드는 실제 객체 타입 기준으로 결정
만약 밭갈기를 출력하고 싶으면?
1. 변수 타입을 Tractor 로 사용
Tractor tractor1 = new Tractor();
tractor1.doWork();
2. 다형성으로 동작
부모를 virtual, 자식을 override 로 바꾼다
class Car
{
public virtual void doWork()
{
Console.WriteLine("단순주행");
}
}
class Tractor : Car
{
public override void doWork()
{
Console.WriteLine("밭갈기");
}
}
출제자의 의도
- 상속을 잘 이해하고 있는지?
- Virtual, override 를 사용하지 않으면 생기는 일을 아는지?
Virtual, override 에 대해 설명해주세요.
- 뜻 자체는 물론, 위 코드 예시처럼 virtual 을 사용하지 않았을 경우 어떤 일이 발생하는지 언급하면 좋다.
- virtual 을 붙이게 되면 어떤 일이 발생하는지 차이를 설명해도 좋다.
가상함수 테이블에 대해 설명해주세요.
- 뜻 그 자체와 Virtual 키워드와 묶어서 설명하면 좋다.
▼ Question 다음 코드의 출력 값 및 이유를 서술하세요.
using System;
class StaticTest
{
static int num;
public StaticTest()
{
num++;
Console.WriteLine("num증가 " + num);
}
static StaticTest()
{
num = 12;
Console.WriteLine("num에 12 대입 " + num);
}
}
class Program
{
static void Main(string[] args)
{
StaticTest single = new StaticTest();
StaticTest bungle = new StaticTest();
}
}
▼ Answer
출력 값 :
num에 12 대입 12
num증가 13
num증가 14
풀이 :
1. static 생성자가 먼저 한 번 실행
static StaticTest()
{
num = 12;
Console.WriteLine("num에 12 대입 " + num);
}
static 생성자는 해당 클래스가 처음 사용될 때 단 한 번만 실행
new StaticTest() 로 객체를 처음 만들기 전에 클래스 초기화가 먼저 일어나므로, 가장 먼저 이 코드가 실행
그래서 첫 출력은 : num에 12 대입 12
2. 첫 번째 객체 생성
그 다음 첫 번째 new StaticTest() 가 실행되면서 인스턴스 생성자 호출
public StaticTest()
{
num++;
Console.WriteLine("num증가 " + num);
}
num 은 static 생성자에서 12가 되어 있으므로 출력은 num증가 13
3. 두 번째 객체 생성
두 번째 new StaticTest() 가 실행되면 다시 인스턴스 생성자가 호출
이때 num 은 static 변수라서 객체마다 따로 있는 게 아니라 클래스 전체가 공유하는 하나의 값이다.
현재 num = 13 이므로 출력은 num증가 14
핵심 개념
static 생성자
- 클래스가 처음 사용될 때 한 번만 실행
- 객체를 만들 때마다 실행되는 것이 아님
static 변수
- 인스턴스마다 따로 생기지 않음
- 클래스 전체가 하나의 값을 공유
출제자의 의도
- Static 을 잘 이해하고 있는지?
- Static 변수, Static 클래스, Static 함수를 잘 이해하고 있는지?
Static 함수에서 비정적 일반 멤버변수를 사용하면 어떻게 되는지 설명해주세요.
- Static 의 특성 및 static 함수가 활용할 수 있는 것들이 무엇이 있는지 설명하면 좋다.
- Static 질문이 들어오면 정적 객체, 또는 변수의 생명주기 언급도 좋다.
▼ Question 다음 코드에서 생길 수 있는 문제에 대하여 서술하세요.
using System;
using System.Collections; // Question 1
using System.Collections.Generic;
class Program
{
static void Main()
{
ArrayList list = new ArrayList();
int number = 42;
list.Add(number);
int retrievedNumber = (int)list[0]; // Question 2
Console.WriteLine("저장된 값: " + retrievedNumber);
}
}
▼ Answer
1. ArrayList 사용 문제
ArrayList list = new ArrayList();
ArrayList 는 비제네릭 컬렉션이라서, 어떤 타입이든 다 넣을 수 있다.
겉보기엔 편하지만 문제가 있다.
- 타입 안정성이 없음
> int 만 넣는다고 보장되지 않는다.
> 나중에 string, double 등 다른 객체를 넣어도 컴파일 에러가 발생하지 않는다.
> 위의 이유로 꺼낼 때 캐스팅 시 문제가 발생한다. - 값 형식 ( int )을 넣을 때 박싱 ( Boxing ) 발생
- 꺼낼 때 다시 언박싱 ( Unboxing ) 발생
성능 면에서도 손해가 있다.
2. 강제 형변환 시 런타임 오류 가능성
int retrievedNumber = (int)list[0];
여기서는 list[0] 의 실제 타입이 정말 int 일 때만 안전하다.
지금 코드에서는 42 를 넣었으니 정상 동작한다.
다른 타입이 들어 있으면 런타임 오류가 발생할 수 있다.
list.Add("42");
int retrievedNumber = (int)list[0];
이 경우에는 컴파일은 되더라도 실행 중에 InvalidCastException 이 발생한다.
- 컴파일 단계에서 막히지 않고 실행하다가 터질 수 있다는 문제가 있다.
핵심 정리
ArrayList 는 비제네릭 컬렉션
- 타입 안정성이 떨어진다
- 박싱 / 언박싱이 발생할 수 있다
(int)list[0] 은 강제 형변환
- 저장된 값이 int 가 아니면 런타임에 예외 발생
출제자의 의도
- 제네릭을 이해하고 있는지?
- 박싱과 언박싱에 대해 이해하고 있는지?
- ArrayList 를 얼마나 이해하고 있는지?
박싱과 언박싱에 대하여 설명해주세요
- 참조, 값 타입, 힙 영역, 스택 영역을 담아서 설명하면 좋다.
- Object 형식에 대한 내용까지 설명하면 좋다.
제네릭을 사용해서 얻을 수 있는 이점에 대해 설명해주세요
- 타입 safe , 런타임 도중 결정, 박싱과 언박싱, where 키워드 등을 설명하면 좋다.
'📖TIL > 🎤Interview' 카테고리의 다른 글
| CS, C++ 등 기타 전산 지식 질문 (0) | 2026.04.08 |
|---|---|
| C# / Unity 통상 질문 (0) | 2026.04.08 |