C#, 잘 모르는 코드 오류를 설명하는 페이지 만들기

소개

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 허용 &nbsp;|&nbsp; string → null 비허용</p>
  </footer>

</div>
</body>
</html>

결과와 배운 점

지금까지는 내가 만든 자료로만 페이지를 만들었다면, 모르는 내용을 질문하고, 그 질문한 내용을 웹으로 만들어 달라고 하여 이해가 더 쉬운 자료를 만들게 되었다는 점입니다.

공부를 할 때 써먹으면 좋겠다는 생각도 들었습니다.

1
밀어주고 끌어주는

온·오프라인 AI 스터디

AI로 어디까지 할 수 있는지
직접 확인하실 분만 신청하세요.