소개
null을 처리할 때 string에 ?를 넣는건 알겠는데 사용자로부터 입력을 받아서 값을 int로 받으면 오류가 계속 출력되어서 이 부분을 claude에게 물어봤습니다.
텍스트로 설명을 해도 되지만 요거 웹 페이지로 만들어줘? 라고 해볼까? 라고 시도를 해 보았습니다.
진행 방법
어떤 도구를 사용했고, 어떻게 활용하셨나요?
Visual Studio를 이용해서 코드를 간략하게 짠 후, 그 코드를 통해서 질문을 했습니다.
Tip: 사용한 프롬프트 전문을 꼭 포함하고, 내용을 짧게 소개해 주세요.
한국사이트 스크린샷
오류 내용을 설명하고, 해결 방법에 대해 이야기를 해주고 질문을 했습니다.
Tip: 활용 이미지나 캡처 화면을 꼭 남겨주세요.
콘솔 판독선
Tip: 코드 전문은 코드블록에 감싸서 작성해주세요. ( / 을 눌러 '코드 블록'을 선택)
<!DOCTYPE html>
<html lang="ko">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0"/>
<title>C# Nullable ReadLine 해결법</title>
<link href="https://fonts.googleapis.com/css2?family=Nanum+Myeongjo:wght@400;700;800&family=JetBrains+Mono:wght@400;600&family=Pretendard:wght@300;400;500;600&display=swap" rel="stylesheet"/>
<style>
:root {
--bg: #0d0f14;
--surface: #13161d;
--surface2: #1a1e28;
--border: #252a38;
--accent: #4af0b0;
--accent2: #f06060;
--accent3: #f0c040;
--accent4: #6090f0;
--text: #e2e8f0;
--text-muted: #6b7a99;
--text-dim: #3d4a66;
}
*, *::before, *::after { box-sizing: border-box; margin: 0; padding: 0; }
body {
background: var(--bg);
color: var(--text);
font-family: 'Pretendard', sans-serif;
font-weight: 400;
line-height: 1.7;
min-height: 100vh;
overflow-x: hidden;
}
/* ── 배경 격자 ── */
body::before {
content: '';
position: fixed;
inset: 0;
background-image:
linear-gradient(rgba(74,240,176,0.03) 1px, transparent 1px),
linear-gradient(90deg, rgba(74,240,176,0.03) 1px, transparent 1px);
background-size: 40px 40px;
pointer-events: none;
z-index: 0;
}
.wrap {
position: relative;
z-index: 1;
max-width: 860px;
margin: 0 auto;
padding: 80px 32px 120px;
}
/* ── 헤더 ── */
header {
margin-bottom: 72px;
}
.tag-line {
font-family: 'JetBrains Mono', monospace;
font-size: 11px;
letter-spacing: 0.25em;
color: var(--accent);
text-transform: uppercase;
margin-bottom: 20px;
display: flex;
align-items: center;
gap: 10px;
}
.tag-line::before {
content: '';
display: inline-block;
width: 28px;
height: 1px;
background: var(--accent);
}
h1 {
font-family: 'Nanum Myeongjo', serif;
font-size: clamp(32px, 5vw, 52px);
font-weight: 800;
line-height: 1.2;
letter-spacing: -0.02em;
margin-bottom: 20px;
}
h1 em {
font-style: normal;
color: var(--accent);
}
.subtitle {
font-size: 15px;
color: var(--text-muted);
max-width: 520px;
line-height: 1.8;
}
/* ── 문제 섹션 ── */
.problem-card {
background: var(--surface);
border: 1px solid var(--border);
border-radius: 12px;
padding: 32px;
margin-bottom: 64px;
position: relative;
overflow: hidden;
}
.problem-card::before {
content: '';
position: absolute;
top: 0; left: 0; right: 0;
height: 2px;
background: linear-gradient(90deg, var(--accent2), transparent);
}
.section-label {
font-family: 'JetBrains Mono', monospace;
font-size: 10px;
letter-spacing: 0.2em;
text-transform: uppercase;
color: var(--accent2);
margin-bottom: 16px;
}
.problem-card h2 {
font-family: 'Nanum Myeongjo', serif;
font-size: 22px;
font-weight: 700;
margin-bottom: 16px;
}
.problem-card p {
font-size: 14px;
color: var(--text-muted);
margin-bottom: 20px;
}
.warning-banner {
background: rgba(240,96,96,0.08);
border: 1px solid rgba(240,96,96,0.25);
border-radius: 8px;
padding: 14px 18px;
font-size: 13px;
color: #f0a0a0;
display: flex;
align-items: flex-start;
gap: 10px;
}
.warning-banner span { font-size: 16px; flex-shrink: 0; margin-top: 1px; }
/* ── 코드 블록 ── */
pre {
background: #080a0f;
border: 1px solid var(--border);
border-radius: 10px;
padding: 24px 28px;
overflow-x: auto;
font-family: 'JetBrains Mono', monospace;
font-size: 13px;
line-height: 1.8;
margin: 16px 0;
position: relative;
}
pre .file-tag {
position: absolute;
top: 10px; right: 14px;
font-size: 10px;
color: var(--text-dim);
letter-spacing: 0.1em;
}
.kw { color: #79b8ff; }
.type { color: #b392f0; }
.fn { color: #79c0ff; }
.str { color: #9ecbff; }
.num { color: #f69d50; }
.cmt { color: #484f5e; font-style: italic; }
.hl { background: rgba(240,96,96,0.15); border-radius: 3px; padding: 1px 3px; }
.hl-g { background: rgba(74,240,176,0.12); border-radius: 3px; padding: 1px 3px; }
/* ── 해결책 그리드 ── */
.solutions-title {
font-family: 'Nanum Myeongjo', serif;
font-size: 26px;
font-weight: 700;
margin-bottom: 8px;
}
.solutions-desc {
font-size: 14px;
color: var(--text-muted);
margin-bottom: 40px;
}
.solution {
background: var(--surface);
border: 1px solid var(--border);
border-radius: 12px;
padding: 28px 32px;
margin-bottom: 24px;
transition: border-color 0.2s, transform 0.2s;
position: relative;
overflow: hidden;
}
.solution:hover {
border-color: rgba(74,240,176,0.3);
transform: translateY(-2px);
}
.solution-header {
display: flex;
align-items: flex-start;
gap: 18px;
margin-bottom: 16px;
}
.badge {
flex-shrink: 0;
width: 40px; height: 40px;
border-radius: 10px;
display: flex;
align-items: center;
justify-content: center;
font-family: 'JetBrains Mono', monospace;
font-size: 13px;
font-weight: 600;
}
.badge-1 { background: rgba(240,192,64,0.12); color: var(--accent3); border: 1px solid rgba(240,192,64,0.25); }
.badge-2 { background: rgba(96,144,240,0.12); color: var(--accent4); border: 1px solid rgba(96,144,240,0.25); }
.badge-3 { background: rgba(74,240,176,0.10); color: var(--accent); border: 1px solid rgba(74,240,176,0.25); }
.solution-meta { flex: 1; }
.solution-meta h3 {
font-family: 'Nanum Myeongjo', serif;
font-size: 18px;
font-weight: 700;
margin-bottom: 4px;
}
.solution-meta p {
font-size: 13px;
color: var(--text-muted);
}
.recommend-chip {
display: inline-flex;
align-items: center;
gap: 5px;
padding: 4px 10px;
border-radius: 20px;
font-size: 11px;
font-weight: 600;
letter-spacing: 0.05em;
}
.chip-simple { background: rgba(240,192,64,0.12); color: var(--accent3); }
.chip-safe { background: rgba(96,144,240,0.12); color: var(--accent4); }
.chip-best { background: rgba(74,240,176,0.12); color: var(--accent); }
.solution-explain {
font-size: 13.5px;
color: var(--text-muted);
background: var(--surface2);
border-left: 3px solid var(--border);
border-radius: 0 6px 6px 0;
padding: 12px 16px;
margin-top: 16px;
}
.solution-explain strong { color: var(--text); }
/* ── 비교 테이블 ── */
.compare-section {
margin-top: 72px;
}
.compare-section h2 {
font-family: 'Nanum Myeongjo', serif;
font-size: 24px;
font-weight: 700;
margin-bottom: 24px;
}
table {
width: 100%;
border-collapse: collapse;
font-size: 13.5px;
}
thead tr {
border-bottom: 1px solid var(--border);
}
thead th {
padding: 12px 16px;
text-align: left;
font-family: 'JetBrains Mono', monospace;
font-size: 11px;
letter-spacing: 0.12em;
text-transform: uppercase;
color: var(--text-muted);
font-weight: 400;
}
tbody tr {
border-bottom: 1px solid var(--border);
transition: background 0.15s;
}
tbody tr:last-child { border-bottom: none; }
tbody tr:hover { background: var(--surface2); }
td {
padding: 14px 16px;
vertical-align: middle;
}
td:first-child {
font-family: 'JetBrains Mono', monospace;
font-size: 12px;
color: var(--accent);
}
.dot-good { color: #4af0b0; }
.dot-warn { color: #f0c040; }
.dot-bad { color: #f06060; }
/* ── 팁 카드 ── */
.tip-card {
margin-top: 48px;
background: rgba(74,240,176,0.05);
border: 1px solid rgba(74,240,176,0.2);
border-radius: 12px;
padding: 24px 28px;
display: flex;
gap: 16px;
align-items: flex-start;
}
.tip-icon { font-size: 22px; flex-shrink: 0; margin-top: 2px; }
.tip-card h4 { font-size: 14px; font-weight: 600; color: var(--accent); margin-bottom: 6px; }
.tip-card p { font-size: 13px; color: var(--text-muted); line-height: 1.7; }
/* ── 푸터 ── */
footer {
margin-top: 96px;
padding-top: 32px;
border-top: 1px solid var(--border);
display: flex;
justify-content: space-between;
align-items: center;
flex-wrap: wrap;
gap: 12px;
}
footer p { font-size: 12px; color: var(--text-dim); font-family: 'JetBrains Mono', monospace; }
/* ── 진입 애니메이션 ── */
@keyframes fadeUp {
from { opacity: 0; transform: translateY(24px); }
to { opacity: 1; transform: translateY(0); }
}
header { animation: fadeUp 0.6s ease both; }
.problem-card { animation: fadeUp 0.6s 0.15s ease both; }
.solution:nth-child(1) { animation: fadeUp 0.5s 0.25s ease both; }
.solution:nth-child(2) { animation: fadeUp 0.5s 0.35s ease both; }
.solution:nth-child(3) { animation: fadeUp 0.5s 0.45s ease both; }
.compare-section { animation: fadeUp 0.5s 0.5s ease both; }
</style>
</head>
<body>
<div class="wrap">
<!-- 헤더 -->
<header>
<div class="tag-line">C# Nullable Reference Types</div>
<h1><em>Console.ReadLine()</em>의<br>밑줄 경고, 어떻게 해결할까?</h1>
<p class="subtitle">
C# 8.0부터 도입된 Nullable Reference Types 기능 때문에 발생하는 경고입니다.
<code style="font-family:'JetBrains Mono',monospace;font-size:12px;color:var(--accent)">Console.ReadLine()</code>이
<code style="font-family:'JetBrains Mono',monospace;font-size:12px;color:var(--accent)">string?</code>을 반환하기 때문에
<code style="font-family:'JetBrains Mono',monospace;font-size:12px;color:var(--accent)">int.Parse()</code>에 바로 넣으면 경고가 뜹니다.
</p>
</header>
<!-- 문제 코드 -->
<div class="problem-card">
<div class="section-label">⚠ 문제 상황</div>
<h2>경고가 발생하는 코드</h2>
<p>아래 코드에서 <strong>Console.ReadLine()</strong> 아래에 밑줄이 생기며 경고가 표시됩니다.</p>
<pre><span class="kw">public void</span> <span class="fn">grade</span>()
{
Console.<span class="fn">Write</span>(<span class="str">"점수를 입력하세요: "</span>);
<span class="kw">int</span> score = <span class="kw">int</span>.<span class="fn">Parse</span>(<span class="hl">Console.<span class="fn">ReadLine</span>()</span>); <span class="cmt">// ← 여기에 밑줄 경고!</span>
<span class="kw">if</span> (score >= <span class="num">60</span>)
Console.<span class="fn">WriteLine</span>(<span class="str">"합격"</span>);
<span class="kw">else</span>
Console.<span class="fn">WriteLine</span>(<span class="str">"불합격"</span>);
}</pre>
<div class="warning-banner">
<span>💡</span>
<div>
<strong>왜 경고가 뜰까?</strong><br>
<code style="font-family:'JetBrains Mono',monospace;font-size:12px">Console.ReadLine()</code>의 반환 타입은 <code style="font-family:'JetBrains Mono',monospace;font-size:12px">string?</code>입니다. 즉, <strong>null이 반환될 수도 있는데</strong>, <code style="font-family:'JetBrains Mono',monospace;font-size:12px">int.Parse(null)</code>은 런타임 오류를 일으키기 때문에 컴파일러가 사전에 경고를 줍니다.
</div>
</div>
</div>
<!-- 해결책 -->
<div class="solutions-title">해결 방법 3가지</div>
<p class="solutions-desc">상황에 맞는 방법을 골라 사용하세요.</p>
<!-- 방법 1 -->
<div class="solution">
<div class="solution-header">
<div class="badge badge-1">01</div>
<div class="solution-meta">
<h3><code style="font-family:'JetBrains Mono',monospace;font-size:14px">!</code> — Null 아님 단언 연산자</h3>
<p>컴파일러에게 "null이 아님을 내가 보장한다"고 알려주는 방식</p>
</div>
<div class="recommend-chip chip-simple">⚡ 가장 간단</div>
</div>
<pre><span class="kw">int</span> score = <span class="kw">int</span>.<span class="fn">Parse</span>(Console.<span class="fn">ReadLine</span>()<span class="hl-g">!</span>);
<span class="cmt">// ! 연산자 하나로 경고가 사라집니다</span></pre>
<div class="solution-explain">
<strong>동작 원리:</strong> <code style="font-family:'JetBrains Mono',monospace;font-size:12px">!</code>(null-forgiving operator)는 컴파일러의 null 분석을 억제합니다. 실제로 null 체크를 하지는 않으므로, null이 들어오면 여전히 런타임 오류가 납니다. 수업용 간단 예제에 적합합니다.
</div>
</div>
<!-- 방법 2 -->
<div class="solution">
<div class="solution-header">
<div class="badge badge-2">02</div>
<div class="solution-meta">
<h3><code style="font-family:'JetBrains Mono',monospace;font-size:14px">??</code> — Null 병합 연산자</h3>
<p>null일 경우 대신 사용할 기본값을 지정하는 방식</p>
</div>
<div class="recommend-chip chip-safe">🛡 안전한 기본값</div>
</div>
<pre><span class="kw">int</span> score = <span class="kw">int</span>.<span class="fn">Parse</span>(Console.<span class="fn">ReadLine</span>() <span class="hl-g">??</span> <span class="str">"0"</span>);
<span class="cmt">// ReadLine()이 null이면 "0"을 대신 사용</span></pre>
<div class="solution-explain">
<strong>동작 원리:</strong> <code style="font-family:'JetBrains Mono',monospace;font-size:12px">??</code>(null-coalescing operator)는 왼쪽이 null이면 오른쪽 값을 반환합니다. null은 처리되지만 숫자가 아닌 문자를 입력하면 여전히 오류가 납니다.
</div>
</div>
<!-- 방법 3 -->
<div class="solution">
<div class="solution-header">
<div class="badge badge-3">03</div>
<div class="solution-meta">
<h3><code style="font-family:'JetBrains Mono',monospace;font-size:14px">int.TryParse()</code> — 안전한 파싱</h3>
<p>null이나 잘못된 입력 모두 안전하게 처리하는 방식</p>
</div>
<div class="recommend-chip chip-best">✅ 가장 권장</div>
</div>
<pre><span class="kw">public void</span> <span class="fn">grade</span>()
{
Console.<span class="fn">Write</span>(<span class="str">"점수를 입력하세요: "</span>);
<span class="hl-g"><span class="kw">if</span> (!<span class="kw">int</span>.<span class="fn">TryParse</span>(Console.<span class="fn">ReadLine</span>(), <span class="kw">out int</span> score))</span>
{
Console.<span class="fn">WriteLine</span>(<span class="str">"올바른 숫자를 입력하세요."</span>);
<span class="kw">return</span>;
}
<span class="kw">if</span> (score >= <span class="num">60</span>)
Console.<span class="fn">WriteLine</span>(<span class="str">"합격"</span>);
<span class="kw">else</span>
Console.<span class="fn">WriteLine</span>(<span class="str">"불합격"</span>);
}</pre>
<div class="solution-explain">
<strong>동작 원리:</strong> <code style="font-family:'JetBrains Mono',monospace;font-size:12px">TryParse()</code>는 변환에 성공하면 <code style="font-family:'JetBrains Mono',monospace;font-size:12px">true</code>를, 실패하면 <code style="font-family:'JetBrains Mono',monospace;font-size:12px">false</code>를 반환합니다. null이든 "abc"든 프로그램이 <strong>절대 터지지 않습니다</strong>. 실무에서 권장하는 방법입니다.
</div>
</div>
<!-- 비교 테이블 -->
<div class="compare-section">
<h2>세 가지 방법 비교</h2>
<table>
<thead>
<tr>
<th>방법</th>
<th>코드 길이</th>
<th>null 처리</th>
<th>잘못된 입력 처리</th>
<th>추천 상황</th>
</tr>
</thead>
<tbody>
<tr>
<td>ReadLine()!</td>
<td><span class="dot-good">●</span> 짧음</td>
<td><span class="dot-bad">●</span> 안됨</td>
<td><span class="dot-bad">●</span> 안됨</td>
<td>수업 예제</td>
</tr>
<tr>
<td>ReadLine() ?? "0"</td>
<td><span class="dot-good">●</span> 짧음</td>
<td><span class="dot-good">●</span> 됨</td>
<td><span class="dot-bad">●</span> 안됨</td>
<td>null만 대비</td>
</tr>
<tr>
<td>int.TryParse()</td>
<td><span class="dot-warn">●</span> 보통</td>
<td><span class="dot-good">●</span> 됨</td>
<td><span class="dot-good">●</span> 됨</td>
<td>실무 / 안전한 코드</td>
</tr>
</tbody>
</table>
<!-- 팁 -->
<div class="tip-card">
<div class="tip-icon">🎓</div>
<div>
<h4>수업에서의 선택 기준</h4>
<p>
처음 배우는 단계라면 <strong style="color:var(--accent3)">방법 1(<code style="font-family:'JetBrains Mono',monospace">!</code>)</strong>로 경고를 빠르게 없애고 핵심 로직에 집중하세요.
입력 유효성 검사를 배우는 단계라면 <strong style="color:var(--accent)">방법 3(TryParse)</strong>을 익혀두면 실무에서도 바로 쓸 수 있습니다.
</p>
</div>
</div>
</div>
<!-- 푸터 -->
<footer>
<p>// C# Nullable Reference Types — C# 8.0+</p>
<p>string? → null 허용 | string → null 비허용</p>
</footer>
</div>
</body>
</html>
결과와 배운 점
지금까지는 내가 만든 자료로만 페이지를 만들었다면, 모르는 내용을 질문하고, 그 질문한 내용을 웹으로 만들어 달라고 하여 이해가 더 쉬운 자료를 만들게 되었다는 점입니다.
공부를 할 때 써먹으면 좋겠다는 생각도 들었습니다.