클리엘
CLIEL LAB
클리엘
전체 방문자
오늘
어제
  • 분류 전체보기 (514) N
    • Mobile (47)
      • Kotlin (47)
    • Web (84)
      • NestJS (9)
      • HTML5 & CSS3 (38)
      • Javascript (20)
      • TypeScript (6)
      • JQuery (11)
    • .NET (301) N
      • C# (84) N
      • ASP.NET (67)
      • Windows API for .NET (128)
    • Server (53)
      • SQL Server (10)
      • MariaDB (18)
      • Windows Server (6)
      • node.js (19)
    • System (12)
      • 작업LOG (12)
    • Review (11)
    • ETC (6)

블로그 메뉴

  • 홈
  • 태그
  • 방명록

공지사항

  • 블로그 정리

인기 글

태그

  • CSS3
  • asp.net core
  • NestJS
  • c#
  • HTML5
  • android
  • Entity Framework
  • node.js
  • LINQ
  • exception
  • Windows API
  • MariaDB
  • android studio
  • jQuery
  • .NET
  • 변수
  • ASP.NET
  • JavaScript
  • asp.net core web api
  • Kotlin

최근 댓글

최근 글

티스토리

hELLO · Designed By 정상우.
클리엘

CLIEL LAB

[C# 13과 .NET 9] 2. C#
.NET/C#

[C# 13과 .NET 9] 2. C#

2025. 5. 13. 14:22
728x90

여기서는 C# 언어에 대한 기본사항들을 알아보고자 합니다. C# Programming에서 자주 사용되는 몇 가지 일반적인 용어와 함께 C# 문법을 사용해 구문을 작성하는 방법을 배워볼 것이며 이 과정에서 computer memory에 data를 저장하고 이를 다루는 방법에 대해서도 알게 될 것입니다.

 

1. C#


이번에 알아볼 내용은 Application개발을 위해 필요한 C#언어의 source code작성시, 필요한 문법과 용어에 대한 것입니다. Programming언어는 특별하게 뭔가 다른 것은 아니고 자신이 직접 필요한 단어를 구성할 수 있다는 것을 제외하고는 사람의 언어(특히 영어)와 비슷한 점이 많습니다.


1) C#의 최신기능 알아보기


C# 13에 관한 새로운 사항은 아래 link를 통해 간단히 살펴볼 수 있으므로 해당 link로 관련사항을 대체하고자 합니다.
https://learn.microsoft.com/en-us/dotnet/csharp/whats-new/csharp-13

 

What's new in C# 13

Get an overview of the new features in C# 13.

learn.microsoft.com

 

2) C# 표준

 

몇해에 걸쳐 Microsoft는 ECMA표준에 C#의 새로운 기능을 제출해 왔으며 2014년에 Microsoft는 C#을 open source화 하였습니다. 가장 최근의 C#표준에 관한 문서는 아래 link에서 확인할 수 있습니다.
https://learn.microsoft.com/en-us/dotnet/csharp/specification/

 

Specifications - ECMA specification and latest features.

Read the detailed specifications for the C# language and the latest features. The specifications are the definitive source for the behavior of the C# language.

learn.microsoft.com

 

ECMA표준보다 더 실질적으로 유용한 것은 C#과 관련기술에 대한 작업을 가능한 한 공개적으로 만들기 위한 공개 GitHub Repository입니다.

C# 언어 설계 https://github.com/dotnet/csharplang
Compiler 구현 https://github.com/dotnet/roslyn
언어에 대한 표준설명 https://github.com/dotnet/csharpstandard

 

2. C# Compiler version


Roslyn으로 알려진 C#과 Visual Basic용 .NET 언어 compiler는 별도의 F# compiler와 함께 .NET SDK의 일부로 배포됩니다. 만약 특정 version의 C# compiler를 사용하고자 한다면 아래 표에 나타난 것처럼 최소한 해당 version의 .NET SDK가 설치되어 있어야 합니다.

.NET SDK Roslyn compiler Default C# language
1.0.4 2.0-2.2 7.0
1.1.4 2.3-2.4 7.1
2.1.2 2.6-2.7 7.2
2.1.200 2.8-2.10 7.3
3.0 3.0-3.4 8.0
5.0 3.8 9.0
6.0 4.0 10.0
7.0 4.4 11.0
8.0 4.8 12.0
9.0 4.12 13.0

 

class library를 생성할때는 가장 최신의 .NET version뿐만 아니라 .NET Standard를 대상으로도 생성할 수 있습니다. 이 경우 기본 C#언어 version은 다음과 같습니다.

.NET Standard C#
2.0 7.3
2.1 8.0


특정 version의 compiler를 사용하고자 한다면 해당 .NET SDK의 최소 version을 설치해야 합니다. 다만 높은 version의 .NET SDK가 설치되어 있다면 생성한 project가 예전 .NET version을 target으로 한다고 하더라도 최신의 compiler version을 사용할 수 있습니다. 예를 들어 .NET 9 SDK나 그 이후의 version이 설치되어 있는 상태라면 .NET 8을 target으로 한 console app에서 C# 13 언어기능을 사용할 수 있습니다.


1) SDK version 확인하기

 

사용가능한 .NET SDK와 C# 언어 compiler version을 확인하고자 한다면 Windows Terminal이나 macOS의 Terminal에서 아래 명령을 사용합니다.

dotnet --version


현재 시점에서 제 computer에 위 명령을 사용하면 아래와 같은 응답이 출력됩니다.

9.0.200

 

이를 통해 설치된 SDK는 초기 version에서 새로운 기능이 추가된 상태임을 알 수 있습니다.


2) 특정 언어 version compiler 사용하기


Visual Studio나 dotnet command-line interface와 같은 개발자도구는 기본적으로 가장 최신의 C# 언어 compiler version을 사용한다고 가정하고 시작합니다. 예를 들어 C# 8이 release 되기 전에는 C# 7이 최신이자 기본적으로 사용되던 version이었습니다.


C# 7에서도 7.1, 7.2, 7.3과 같은 경우, 소수점으로 향상되는 version을 사용하고자 한다면 project file에서 아래와 같은 설정 요소를 추가시켜 줄 수 있습니다.

<LangVersion>7.3</LangVersion>


따라서 .NET 9과 함께 C# 13이 release 된 후 Microsoft가 C# 13.1을 release 했고 이에 대한 새로운 기능을 사용하고자 한다면 project file로 아래와 같이 설정 요소를 추가시켜줘야 합니다.

<LangVersion>13.1</LangVersion>


<LangVersion>에 설정할 수 있는 값은 아래 표를 참고하시면 됩니다.

<LangVersion> 설명
7, 7.1, 7.2, 7.3, 8, 9, 10, 11, 12, 13 해당 version이 설치된 경우 compiler가 사용할 특정 version번호를 명시적으로 지정합니다.
latestmajor 가장 높은 최신의 주 version 번호를 사용합니다. 예를 들어 2019년 8월에는 7.0이되고 2019년 10월에는 8이, 2020년 11월에는 9, 2021년 11월에는 10, 2022년 11월에는 11, 2023년 11월에는 12가, 2024년 11월에는 13이 될 수 있습니다.
latest 가장 높은 최신의 version을 사용합니다. 예를 들어 2017년에는 7.2, 2018년에는 7.3, 2019년에는 8, 2025년 현재까지는 13이 될 수 있습니다.
preview 가장 높은 preview version을 사용합니다. 예를 들어 2025년 7월중순쯤에 .NET 10 Preview 6를 설치했다면 14가 될 수 있습니다.

 

3) Preview C# compiler 사용하기


2025년 2월 Microsoft는 C# 14 compiler와 함께 .NET 10에 대한 첫 번째 공개 preview version를 release 하였습니다. 해당 preview는 아래 link를 통해 내려받아 설치할 수 있습니다.
https://dotnet.microsoft.com/en-us/download/dotnet/10.0

 

Download .NET 10.0 (Linux, macOS, and Windows)

.NET 10.0 downloads for Linux, macOS, and Windows. .NET is a free, cross-platform, open-source developer platform for building many different types of applications.

dotnet.microsoft.com

 

.NET 10 SDK preview를 설치하고 나면 이를 통해 새로운 project를 생성하고 C# 14에 대한 신규 기능을 살펴볼 수 있습니다. 이를 위해 새로운 project를 생성하고 나면 .csproj project file에 <LangVersion> 요소를 추가하여 C# 14 compiler preview를 사용하도록 아래와 같이 지정합니다.

<OutputType>Exe</OutputType>
<TargetFramework>net10.0</TargetFramework>
<LangVersion>preview</LangVersion>
production project가 아닌 preview를 살펴보기 위한 목적으로만 <LangVersion>을 추가해야 합니다. preview는 Microsoft에 의해 지원되는 것이 아니며 생각보다 많은 bug를 가질 수 있습니다. Microsoft가 preview를 배포하는 목적은 오로지 우리 같은 개발자들에게 feedback를 듣기 위함일 뿐입니다.

 

4) .NET 9의 C# compiler를 향후 version으로 전환하기


.NET 9는 C# 13 compiler와 함께 제공되지만 그렇다고 해서 C# 13 compiler만 사용해야 한다는 것은 아닙니다. 추후(2025년 11월쯤) .NET 10 SDK가 GA(Generally Available)로 release 되고 나면 이 2가지의 모든 것을 같이 누릴 수 있습니다.


때문에 project가 .NET 9를 target으로 하는 동안에도 .NET 10 SDK와 C# 14 compiler를 사용할 수 있는데, 이렇게 하기 위해서는 target framework를 net9.0으로 설정하고 <LangVersion> 요소를 14로 설정합니다.

...생략
<OutputType>Exe</OutputType>
<TargetFramework>net9.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
<LangVersion>14</LangVersion>
...생략


상기 project target은 9.0이므로 매달 patch되는 .NET 9 runtime에서 실행하는 경우 2026년 5월까지 지원됩니다. 만약 이 project를 .NET 10 SDK를 사용해 build 하는 경우라면 C# 14를 의미하는 <LangVersion>을 14로 설정할 수 있습니다. 다만 .NET 10 SDK를 설치하고 신규 project의 기본 target인 net10으로 설정되면 기본언어 역시 C# 14가 되는데 그러면 이를 명시적으로 설정할 필요는 없습니다.


2026년 2월에 Microsoft는 .NET 11에 대한 첫 번째 preview를 release 할 것으로 보이며 그러면 2026년 11월에 .NET 11이 GA로 전환될 것입니다. 그때 아래 link를 통해 해당 SDK를 설치하고 C# 14와 .NET 10에 상술한 방법으로 C# 15를 사용해 볼 수 있습니다.
https://dotnet.microsoft.com/en-us/download/dotnet/11.0

상기 link를 향후에 사용을 위한 것입니다. 따라서 지금 위 URL에 접속하면 404 Error를 보게 됩니다.
일부 C#언어기능은 .NET Library의 변화에 의존합니다. 만약 최신의 compiler와 함께 최신의 SDK를 사용할 때 이전 .NET의 version으로 target을 맞추지 않으면 새로운 언어기능을 사용하지 못할 수 있습니다. 예를 들어 C# 11에서 required keyword를 도입했지만 해당 언어기능은 .NET 7 이상에서만 가능한 새로운 attribute를 필요로 하므로 .NET 6을 target으로 하는 project의 경우에는 사용할 수 없습니다. 이런 사유로 지원되지 않는 C#언어기능을 사용하려 한다면 compiler는 경고 message를 표시하게 됩니다.

 

5) compiler version 확인하기


Compiler version을 확인해 보기 위해 우선 CS folder안에서 Study-02라는 folder를 만들고 그 안에 voca이름의 C# console app project를 생성합니다. 이때 Do net use top-level statements와 Enable native AOT publish는 check하지 않습니다.


project의 Program.cs에서 아래 문을 추가합니다.

#error version


Visual Studio라면 Debug -> Start Without Debugging button을 눌러 project를 실행하고 VS Code라면 terminal을 열고 voca project folder위치에서 dotnet run명령을 실행합니다.


project를 실행하면 compiler error가 발생할텐데 걱정하지 말고 현재 발생한 compiler error message(CS8304)를 잘 보고 compiler version과 lanauage version을 확인해 봅니다.


Visual Studio라면 Error List window에 표시될 것이며 Terminal이라면 아래와 같이 관련 오류가 표시될 것입니다.

 

message내용에 의하면 Compiler version은 4.13.0이며 언어 version은 13 임을 알 수 있습니다. 이제 위에서 추가한 문을 삭제하거나 주석처리합니다.

3. C# 문법과 용어


이제 C#에 대한 기본적인 문법과 용어를 살펴보는 것으로 시작하겠습니다. 이 과정동안 다수의 console app project를 생성할 것이며 각각의 project에서 C#언어와 관련된 기능을 구현할 것입니다.

 

1) C# 문법

 

C#에서 문법은 구문과 block을 기반으로 구성됩니다. 또한 추가적으로 code의 설명을 위해 주석을 사용할 수 있습니다.

주석이 code를 설명하기 위한 유일한 방법은 아닙니다. 변수나 함수이름을 짓는것 자체도 해당되며 단위 test를 작성하는 등 여러 가지 방법으로 code를 문서화할 수 있습니다.

 

2) 구문


영어나 한글등 기타 많은 언어에서 문장의 끝을 나타내기 위해 마침표(.)를 사용합니다. 문장은 또한 다수의 단어와 문구로 구성될 수 있으며 이때 단어의 순서는 고유의 문법을 준수하여 나열되어야 합니다. 예를 들어 영어에서 다음과 같은 문장이 있다면


"the yellow car."

 

형용사 yellow는 명사 car앞에 와야 합니다. 그런데 프랑스어 문법에서는 다른 순서를 가지고 있어서 형용사가 명사다음에 와서 다음과 같은 문장을 나타낼 수 있습니다.


"la voiture jaune."

 

여기서 중요한 것은 순서가 지켜져야 한다는 것이며 이러한 순서규칙은 C#에서도 적용됩니다.


C#에서 구문의 끝은 semicolon(;)으로 나타냅니다. 이때 구문은 다수의 type과 변수, 표현식으로 구성될 수 있으며 이때 각각의 표시는 공백이나 +, =와 같은 연산자처럼 서로 다른 표시임을 인식할 수 있는 방법으로 분리됩니다.


예를 들어 아래 문에서 int은 type이며 totalSum은 변수이고 10 + 20은 표현식에 해당합니다.

int totalSum = 10 + 20;


이때 표현식은 10이라는 피연산자와 +연산자, 그리고 20이라는 또 다른 피연산자로 구성됩니다. 이때도 피연산자와 연산자의 순서는 다른 의미와 결과를 가져올 수 있기 때문에 중요한 부분에 해당합니다.

 

3) 주석

 

주석은 code에 설명을 달아 다른 개발자들이 해당 주석을 읽고 이것이 어떻게 동작하는지를 더 잘 이해할 수 있도록 하기 위한 가장 주요한 방법중 하나입니다. 또한 굳이 다른 개발자가 아니어도 추후에 자신의 code를 다시 읽어볼 때 그 code의 작성 의도를 파악하는데도 도움이 될 수 있습니다.

///으로 시작하는 XML주석방법도 존재합니다. 특히 Web API를 만들때 외부에 해당 API의 기능을 설명하기 위한 용도로도 많이 사용되는데, XML주석에 관해서는 추후에 자세히 알아볼 것입니다.


주석은 아래와 같이 //통해 작성할 수 있으며 compiler는 //이후부터 행 끝까지 모든 것을 무시합니다.

//합계 계산
int totalSum = 10 + 20;


만약 주석을 두줄이상에 걸쳐 작성해야 한다면 아래와 같이 /*과 */를 사용할 수 있으며 compiler는 /*부터 */까지 모든 것을 무시하게 됩니다.

/*
-= 합계 =-
-- 계산 --
*/

 

대부분의 경우 /*와 */는 여러줄에 걸쳐 사용되지만 아래와 같은 경우 역시 가능합니다.

int totalSum = 10 /*피연산자*/ + 20;

 

굳이 주석을 달지 않더라도 함수나 변수의 이름을 잘 짓는것만으로 code의 설명이 가능해질 수 있습니다. 다시 말해 주석은 너무 많거나 길면 오히려 code를 이해하는데 역효과를 줄 수 있으니 적당한 선을 유지하는 것이 좋습니다.


대부분의 code editor의 경우 주석을 추가하거나 삭제하기 쉽도록 하는 자체기능을 갖고 있을 수 있습니다. 예를 들어 Visual Studio의 경우 Edit -> Advenced -> Comment Selection과 Uncomment Selection을 사용할 수 있으며 VS Code라면 Eidt -> Toggle Line Comment와 Toggle Block Comment를 사용할 수 있습니다.

4) Block


영어나 한글과 같은 대부분의 언어에서 새로운 단락은 새로운 행을 추가함으로써 나타내는 경우가 많습니다. C#에서 code의 block은 중괄호({})를 사용함으로써 나타냅니다.


Block은 무엇인가가 정의되는 것을 나타내는 선언으로 시작합니다. 예를 들어 namespace, class, method또는 foreach문과 같이 다수의 언어구조에서 시작과 끝을 정의할 수 있습니다.


namespace, class, method와 같은 것은 추후에 자세히 알아보겠지만 간단히 요약하면 namespace는 class와 같은 type을 함께 group 화하기 위한 것이며 class는 method를 포함해 개체의 member를 포함하고, method는 개체가 취할 수 있는 동작의 구현을 포함합니다.


Visual Studio나 VS Code와 같은 code editor는 mouse pointer를 code의 왼쪽여백에 올려 화살표 모양의 문자를 아래 혹은 오른쪽으로 전환함으로써 block을 접거나 펼 수 있는 편리한 기능을 제공하고 있습니다.

 

5) Region


code editor상의 어떤 구문이든 자신이 원하는 구문의 영역을 잡고 여기에 짧은 comment를 부여할 수 있는데 이런 방법으로 구분된 영역을 region이라고 합니다. 이 기능은 대부분의 code editor에서 지원하고 있으며 이를 통해 block처럼 code를 접거나 다시 펼칠 수 있습니다.

#region 매출세액 계산
int tax = 10;
int price = 1000;
decimal result = (price / 1.1) * tax;
#endregion


이 방법은 code를 접은상태로 표시할 때 해당 code block이 어떤 동작을 수행하는지에 대한 요약정보를 표시함으로써 block에 대한 주석으로도 활용될 수 있습니다.


6) 구문와 block의 사례

 

top-level program기능을 사용하지 않은 단순한 console app project를 통해소도 code에서 구문과 block이 어떻게 사용되는지 볼 수 있습니다.

namespace MyEnviroment;

class Program
{
    static void Main(string[] args)
    {
        Console.WriteLine(Environment.CurrentDirectory);
        Console.WriteLine(Environment.OSVersion.VersionString);
        Console.WriteLine("Namespace: {0}", typeof(Program).Namespace ?? string.Empty);
    }
}


또한 C#에서 괄호는 하나의 line에서, 그리고 동일한 들여 쓰기 수준에서 열고 닫는 게 일반적입니다.

if (a == b)
{
	//
}


물론 Javascript와 같은 다른 언어에서도 대부분 같은 규칙이 적용되지만 구문이 선언되는 끝부분에 여는 중괄호를 사용함으로써 세로간격을 줄이는 경우도 있습니다.

if (a == b) {
	//
}


compiler는 어떤 경우는 상관하지 않으므로 자신이 원하는 style을 사용할 수 있습니다.

coding style에 대한 공식문서는 아래의 link에서 확인하실 수 있습니다.
https://learn.microsoft.com/en-us/dotnet/csharp/fundamentals/coding-style/coding-conventions

 

하지만 공식문서의 guidline과는 상관없이 team에 소속되어 개발하는 경우라면 해당 team에서 도입한 표준을 받아들이는 것이 중요합니다. 혼자서 개발하는 경우 compile만 되다면 어떤 선호하는 규칙을 따르든 전혀 문제가 되지 않는데 이때도 중요한 건 일관성을 유지하라는 것입니다.

 Microsoft의 공식문서에서 사용된 괄호 style은 대부분인 C#에서 공통적으로 사용되는 것들입니다. 예를 들어 아래 link를 통해 for문이 사용된 경우를 볼 수 있습니다.
https://learn.microsoft.com/en-us/dotnet/csharp/language-reference/statements/iteration-statements

 

7) 여백을 사용한 code구조화

 

여백이라함은 space, tab, 개행문자등을 포함하는 문자를 말합니다. 이러한 여백은 우리가 원하는 대로 code를 구조화하는 데 사용할 수 있으며 이때 여백은 computer에 어떠한 영향도 주지 않습니다.


아래 4개의 구문은 모두 동일한 것입니다. 때문에 변수의 이름도 같고 그렇기에 동일한 code block내에서 선언될 수 없습니다. 만약 아래 code를 직접 test 하고자 한다면 sum변수의 이름을 모두 변경해야 합니다.

int sum = 1 + 2;
int
sum = 1 +
2;
int sum= 1 +2; int sum=1+2;


위 구문에서 여백문자가 필요한 유일한 부분은 int와 sum사이이며 이를 통해 compiler에게 이들이 서로 다른것임을 말해줄 수 있습니다. code에서는 특별한 경우가 아니면 모든 단일 여백문자(space, tab, 개행등)를 사용할 수 있습니다.

아래 link를 통해 C#의 여백문자에 대한 공직적인 내용을 살펴볼 수 있습니다.
https://learn.microsoft.com/en-us/dotnet/csharp/language-reference/language-specification/lexical-structure#634-white-space

 

8) C# 어휘


C#의 어휘는 keyword, symbol 문자, 그리고 type을 구성됩니다.


C#에서는 자주 사용되는 일부 keyword를 사전정의하고 있는데 대략 using, namespace, class, static, int, string, double, bool, if, switch, break, while, do, for, foreach, this, 그리고 true/false와 같은 것들이 있습니다.

this keyword는 여러가지로 사용됩니다. 개체에 대한 현재 instance를 참조하거나 개체의 instance에 대한 생성자를 호출하거나 indexer를 정의하기도 합니다. 이 3가지 경우에 대해서는 추후에 자세히 살펴볼 것입니다.


symbol 문자는 ", ', +, -, *, /, %, @, $등이 있으며 괄호문자는 아래와 같은 것들이 사용됩니다.

  • ()는 소괄호라고 하는 것으로 함수를 호출하고 표현식이나 조건문을 정의하며 type 간 형변환등 많은 곳에서 사용됩니다.
  • {}는 중괄호라고 하는 것으로 block을 정의하거나 개체와 collection의 초기화를 수행하는 데 사용됩니다.
  • []는 대괄호라고 하는 것으로 array나 collection상에서 item에 접근하거나 code의 element로 attribute를 붙이는 데 사용됩니다.
  • <>는 꺽쇠라고 부르는데 generic type및 XML, HTML등에서 사용됩니다.

이외에 like, or, nor, record, init와 같이 특정 문맥에 따라 특별한 의미를 가지는 문맥상 keyword도 존재합니다.


이런저런 keyword를 소개하긴 했지만 그래도 C#언어에서 keyword는 100여 개 정도에 불과하며 C#언어를 사용하면서 자연스럽게 익히게 될 것이므로 이들도 모두 외우려고 할 필요는 없습니다.

C# keyword는 모두 소문자를 사용합니다. 물론 사용자정의 type의 이름에도 모두 소문자를 사용할 수 있지만 그렇게 하지 않는 것이 좋습니다. C# 11부터 compiler는 class와 같은 type명칭에 소문자만 사용한 경우 CS8981에 대한 경고 messsage를 부여합니다.

 

원하는 경우 C# keyword를 변수명에 사용할 수 있습니다. 다만 이때는 @문자를 접두사로 붙여야 합니다.

int @class = 10;
변수명에 위와 같은 방법으로 C# keyword를 사용할 수 있지만 이러한 방식은 권장하지 않습니다. Micorosft는 C#언어에 대한 새로운 version을 개발할 때마다 field와 같은 keyword를 언어에 추가하려고 하지만 많은 개발자가 변수명을 field로 사용하고 있으며 이런 경우 keyword로 전환 시 project전체에 큰 영향을 주게 되기 이를 실행하는 것이 쉽지가 않습니다.

 

9) 프로그래밍 언어와 일반언어의 비교


영어의 경우 250,000이상의 고유단어가 존재하지만 C#은 불과 100개 정도의 keyword만을 가지고 있습니다. 영어와 비교해 불과 0.0416%밖에 안 되는 단어를 가지고 있음에도 불구하고 C#을 제대로 배우기는 쉽지 않습니다.


영어와 programming언어에서 가장 중요한 차이점은 개발자가 새로운 단어를 새로운 뜻으로 정의할 수 있다는 것입니다. C#에서의 (거의)100여개의 단거를 제외하고서라도 C#을 배울 때 보면 다른 개발자가 정의한 수십, 수만 가지의 단어를 마주하게 되지만 그럼에도 불구하고 여전히 자신만의 '단어'를 정의하는 방법을 배우는 것입니다.


대부분의 programming언어에서는 if와 break와 같은 영어단어를 사용하고 있고 관련한 여러 문서또한 거의 영어로 되어 있는 경우가 많으므로 개발자라면 영어를 배워두는 것이 좋습니다. 물론 Arabic이나 심지어 한글과 같은 언어를 사용하는 programming언어가 존재하지만 이는 매우 휘기한 경우일 뿐입니다.

10) C#구문에 대한 color scheme변경하기


기본적으로 Visual Studio와 VS Code에서는 C# keyword를 파란색으로 표시하여 다른 code에서 이들을 쉽게 구분할 수 있도록 지원하고 있으며 필요하다면 이러한 색상을 변경할 수 있습니다.


Visual Studio인 경우

  • Tools -> Options에서 Environment영역을 찾아 'Fonts and Colors'부분을 선택합니다.
  • 여기서 조정하고자 하는 표시항목을 선택합니다. 이때 전체 영역을 모두 나열해 볼 수 있고 또는 어떤걸 변경할지 정확히 정해져 있다면 검색기능을 통해 검색해 볼 수도 있습니다.

VS Code인 경우

  • File -> Preferences -> Theme -> Color Theme를 선택합니다.
  • 여기서 원하는 color theme를 선택합니다.

11) 정확한 code 작성하기


Notepad와 같은 Plain text editor같은 경우 정확한 영어단어를 작성하는데 전혀 도움을 주지 못합니다. C#언어를 정확하게 작성하는데도 도움을 주지 않는 것도 마찬가지입니다.


Microsoft Word의 경우, 영어를 기준으로 예를 들면 잘못된 spelling의 단어일때 붉은색 밑줄을 긋고 여기에 icecream은 ice-cream 또는 ice cream으로 되어야 한다는 부가적 안내를 추가해 줍니다. 또한 문장의 첫 번째 글자는 대문자여야 한다는 것처럼 문법적 오류역시 파란색 밑줄을 통해 이를 알려줌으로써 영어를 작성하는데 도움을 줄 수 있습니다.


비슷하게 Visual Studio와 VS Code의 C#확장기능역시 WriteLine method의 이름에서 L은 대문자여야 한다거나 문장의 끝은 semicolon으로 끝나야 한다는 문법적 오류와 같이 실수로 작성된 부분을 강조함으로써 C# code를 작성하는데 도움을 줄 수 있습니다. 여기서 C# 확장기능은 지속적으로 개발자가 무엇을 입력하는지 지켜보고 있다가 Microsoft Word와 비슷하게 문제가 되는 부분을 밑줄로 강조함으로써 개발자에게 feedback을 주는 것입니다.


VSCode에서 Study-02 voca에서 작성한 Program.cs file을 열어 WriteLine의 L을 소문자로 바꾸고 문의 끝에 semicolon을 삭제해 봅니다. 그러면 code editor상에서 붉은색 밑줄과 함께 View Problems에서 이에 관한 상세한 내용이 표시되어 있음을 확인할 수 있습니다.

 

12) Importing namespace


System은 type의 주소와 같은 namespace입니다. 어딘가의 위치를 정확히 알려주기 위해 아마도 '서울시 서대문구 서대문구청'과 같이 주소를 알려줄 수 있으며 이를 들은 사람은 서대문구청이 '서울시의 서대문구에 있다.'라는 것을 알 수 있을 것입니다.


System.Console.WriteLine역시 compiler에게 System이름의 namespace에서 Console이름의 type을 찾아 그 안에 있는 WriteLine method를 실행하라라는 의미로 전달할 수 있으며 이를 통해 WriteLine method를 정확히 찾아갈 수 있을 것입니다.


하지만 매번 이런식으로 method를 지정하면 너무 번거롭기 때문에 실제 code에서는, 예를 들어 .NET 6.0 이전에 모든 version의 Console App project template에서는 compiler에게 namespace가 붙지 않는 모든 type의 namespace로 System namespace를 참조시키기 위해 code file상단에 아래와 같은 구문을 추가하였습니다.

using Name;

 

이러한 방법을 namespace import라고 하며 이렇게 하면 namespace접두사를 붙여줄 필요없이 해당 namespace에서 가능한 모든 type을 program에서 사용할 수 있고 code를 입력하는 동안에도 IntelliSense에 관련 type이 노출되어 편리하게 code를 작성할 수 있습니다.


13) namespace를 암시적으로 그리고 전역적으로 import하기


본래 namespace를 import 해야하는 모든 .cs file에서는 가장 먼저 using문을 사용해 필요한 namespace를 import해야 합니다. 이때 System과 System.Linq와 같은 namespace는 거의 대부분의 file에서 필요로 하므로 모든 .cs file의 처음 몇 줄은 최소한 아래와 같은 몇 개의 using구문이 기본적으로 작성되었습니다.

using System;
using System.Linq;
using System.Collections.Generic;


문제는 예를 들어 ASP.NET Core를 사용하여 website나 service를 생성할 때는 여기서 필요한 각각의 .cs file마다 수십 개의 namespace를 import 해야 한다는 것입니다.


때문에 C# 10에 들어서면서 이러한 문제를 해결하고자 신규 keyword를 도입하고 .NET SDK 6에서도 공통 namespace를 간단히 import 하기 위한 새로운 project 설정을 도입하게 되었습니다.


새로 도입된 global using keyword를 사용하면 모든 .cs file이 아닌 하나의 .cs file에서만 namespace를 import 하면 되므로 모든 file에서 동일한 using문을 사용할 필요가 없어졌습니다. 이때 gloabl using문을 Program.cs와 같은 file에 놓을 수 있지만 이렇게 하기보다는 가급적 GlobalUsings.cs와 같은 이름으로 별도의 분리된 file을 통해 오로지 global using문만을 놓는 용도로만 사용할 것을 권장합니다.

global using System;
global using System.Linq;
global using System.Collections.Generic;
gloabl using사용을 위해 file을 만들 때 naming규칙을 잘 살펴보시기 바랍니다. GlobalUsings라는 이름이 아마도 표준 규칙처럼 사용될 수 있으며 곧 보게 되겠지만 관련된 .NET SDK기능에서도 비슷한 이름의 naming규칙을 사용하고 있습니다.


.NET 6 및 이후 version을 대상으로 하는 모든 project는 C# 10과 이후 version의 compiler를 사용합니다. 따라서 이러한 project는 obj/Debug/net9.0 foler에 [project명].GlobalUsings.g.cs file을 생성하여 System과 같은 일부 공통 namespace에 대한 암시적인 전역 import를 구현하고 있습니다. 이때 암시적으로 import 되는 namespace는 대상이 되는 SDK에 맞춰 결정됩니다.

SDK Namespace
Microsoft.NET.Sdk System
System.Collections.Generic
System.IO
System.Linq
System.Net.Http
System.Threading
System.Threading.Tasks
Microsoft.NET.Sdk.Web
(Microsoft.NET.Sdk를 기본으로 추가됨)
System.Net.Http.Json
Microsoft.AspNetCore.Builder
Microsoft.AspNetCore.Hosting
Microsoft.AspNetCore.Http
Microsoft.AspNetCore.Routing
Microsoft.Extensions.Configuration
Microsoft.Extensions.DependencyInjection
Microsoft.Extensions.Hosting
Microsoft.Extensions.Logging
Microsoft.NET.Sdk.Worker
(Microsoft.NET.Sdk를 기본으로 추가됨)
Microsoft.Extensions.Configuration
Microsoft.Extensions.DependencyInjection
Microsoft.Extensions.Hosting
Microsoft.Extensions.Logging

 

이전에 생성한 project에서도 자동적으로 암시적 import가 이루어진 file이 존재하며 해당 file은 obj -> Debug -> net9.0 folder의 voca.GlobalUsings.g.cs file입니다.

// <auto-generated/>
global using global::System;
global using global::System.Collections.Generic;
global using global::System.IO;
global using global::System.Linq;
global using global::System.Net.Http;
global using global::System.Threading;
global using global::System.Threading.Tasks;
해당 file에 적용된 naming규칙은 [project명].GlobalUsings.g.cs입니다. 여기서 g는 generated를 의미하는 것으로 개발자가 작성한 Global file과 구별하기 위한 것입니다.


이 file은 .NET 6 또는 그 이후 version을 대상으로 하는 project를 위해 compiler에서 자동적으로 생성한 file이며 해당 file을 통해 일부 공통적으로 사용되는 namespace들을 import 하고 있습니다.


만약 특정 namespace에 대해서 위에서 처럼 기본적으로 import 될 수 있도록 지정할 필요가 있다면 project의 project file(csproj)에서 아래와 같이 필요한 namespace요소를 지정해 줄 수 있습니다.

<ItemGroup>
	<Using Remove="Systehttp://m.Net.Http" />
	<Using Include="System.Console" Static="true" />
	<Using Include="System.Environment" Alias="Env" />
</ItemGroup>
추가할 부모요소의 이름이 ItemGroup임에 주의하시기 바랍니다. 비슷한 요소로 <ImportGroup>이 있지만 이와는 다른 것입니다.


기존의 열려있던 voca.GlobalUsings.g.cs file을 저장하지 말고 그대로 닫은 뒤 다시 열어보면 System.Net.Http가 없어지고 대신 System.Console과 System.Environment가 import 되어 있음을 확인할 수 있습니다. 또한 System.Environment에는 Env라는 별칭이 지정되어 있고 System.Console은 static으로 import되어 있습니다.


이에 따라 우리는 WriteLine method를 namespace명시하지 않고서도 호출할 수 있으며 Environment 역시 별칭을 지정했으므로 Env만으로 사용할 수 있습니다.

WriteLine($"Computer Name : {Env.MachineName}");


하지만 경우에 따라 자동 import기능이 필요하지 않을 수 있는데 이런 때는 project file(csproj)에서 아래 요소를 삭제하거나

<ImplicitUsings>enable</ImplicitUsings>


아니면 위 요소의 값을 disable로 변경하면 됩니다.

위 설정은 global using문을 사용하는 file이 자동적으로 생성되지 않도록 하는 설정입니다. gloabl using자체를 무효화하는 것이 아니므로 필요한 경우 임의로 file을 생성하고 그 안에서 원하는 namespace를 전역적으로 import 할 수 있습니다.

 

14) Method

 

Method는 영어에서 동사와 유사합니다. 영어에서 동사는 run이나 jump와 같이 어떤 동작을 나타내는 단어인데 이를 C#에서는 method라고 하며 C#에서는 수십만 가지의 method가 존재합니다. 또한 영어에서 동사는 동작이 발생하는 시점에 기반해 쓰이는 방식이 달라질 수 있습니다. 예를 들어 'jennie was jumping in past.', 'Jennie jumps in the present.', 'Jennie jumped in the past.', 'Jennie will jump in the future.'와 같이 시제에 따라 약간씩 변형이 생기는 것입니다.


C#에서 WriteLine와 같은 method는 특정 동작에 기반해 호출되는 방식과 실행되는 방식이 바뀔 수 있습니다. 이것을 overloading이라고 하는데 관련한 내용은 추후에 자세히 알아볼 테지만 아래와 같이 다양한 방식으로 사용될 수 있습니다.

Console.WriteLine(); //줄바꿈 수행, carriage-return 또는 line feed라고 함
Console.WriteLine("Hello"); //지정한 문자열 출력 후 줄바꿈 수행
Console.WriteLine("Current Date : {0}", DateTime.Now); //문자열보간을 사용해 현재 시간 표시 후 줄바꿈 수행


overloading을 정확하지 않지만 조금 다른 비유를 들어 설명하자면, 영어에서 일부 동사는 동일한 철자를 갖지만 문맥에 따라 다른 의미를 가질 수 있습니다. 예를 들어 'you can lose a game.'과 'lose your keys.'에서 lose는 철자가 동일하지만 의미는 다르게 해석됩니다.

 

15) type, 변수, field, 속성


type, 변수, field, 속성은 영어에서 명사와 유사합니다. 영어에서 명사는 어떤 걸 참조하는 이름이 될 수 있는데, 예를 들어 'Spark is the name of car.'에서 car라는 단어는 듣는 이에게 Spark는 어떤 유형에 속하는지를 알려주고 있으며 이를 통해 'I'm going to ride Spark home.'에서와 같이 Spark라는 이름을 사용할 수 있게 됩니다.


영어에서 이것은 type, 변수, field, 속성과 동일하며 아래와 같이 해석될 수 있습니다.

  • Car는 유형이며 어떤 것을 분류하게 위한 명사입니다.
  • Engine과 Transmission은 field나 속성이 될 수 있으며 이들은 Car가 어떤 것으로 이루어졌는지에 대한 명사입니다.
  • Spark는 변수입니다. 즉, 특정한 개체를 참조하기 위한 명사에 해당합니다.

C#에서는 string이나 int와 같은 type으로 소수의 keyword만 가지고 있으며 그 외 어떠한 type도 정의하지 않습니다. type처럼 보이는 string keyword는 type에 대한 별칭으로 사용되며 C#이 동작하는 platform에서 제공하는 type을 나타냅니다.


이것은 C#이 독립적으로 존재할 수 없다는 것을 알려주는 중요한 부분에 해당합니다. C#은 .NET상에서 동작하는 언어일 뿐이며 이론적으로 누군가가 다른 platform상에서 동작하는 C#에 대한 compiler를 만들어 System.Int32(int라는 별칭에 연결되는 C# keyword)및 System.Xml.Linq.XDocument와 같은 복잡한 type 포함해 수십만 가지에 이르는 type을 제공할 수도 있습니다.

 

즉, .NET이라는 platform에서 System.Int32와 같은 type을 제공하는 것이고 C#은 이를 int와 같은 별칭을 통해 해당 type을 사용할 뿐입니다. C#이 이러한 type을 가지는 것이 아닙니다.


type이라는 용어는 종종 class와 혼동하기 쉬운데 정확히 C#에서 모든 type은 class, struct, enum, interface, delegate로 분류될 수 있습니다.(이런 것들에 대해서는 나중에 자세히 알아볼 것입니다.) 예를 들어 string이라는 C# keyword는 class에 해당하지만 int는 구조체(struct)입니다. 이때 type이라는 말은 이 둘을 모두 언급하기에 가장 적절한 용어라고 할 수 있습니다.

 

16) C# 어휘의 범위 알아보기

 

C#에는 100개 이상의 keyword가 존재하며 여기에는 수많은 type이 포함되어 있습니다. 이전에 만든 console app project에서 약간의 code를 추가하여 C#에서 사용가능한 type이 얼마나 있는지 알아보겠습니다.


물론 아직까지는 아래 code가 어떻게 동작하는지 정확하게 알고 있을 필요는 없습니다. 참고로만 말씀드리면 아래 code에서 목적한 바를 달성하기 위해 reflection이라고 하는 방법을 사용하였습니다.

 

우선 Program.cs에서 현재 존재하는 모든 code를 주석처리합니다. 그리고 file의 상단에 System.Reflection namespace를 import 합니다. 이를 통해 우리는 Assembly와 TypeName과 같은 namespace의 일부 type을 사용할 수 있습니다.

using System.Reflection;
이것 역시 project전체에 걸쳐 모든 .cs file에 적용할 수 있도록 global using기능을 사용해 해당 namespace를 암시적으로 import 할 수 있습니다. 하지만 예제에서는 단 하나의 file만 적용하면 되기 때문에 필요로 하는 namespace를 해당 file에서 명시적으로 import 하는 것이 훨씬 더 나은 방법입니다.


compile 된 console app을 가져와 여기서 접근하고 있는 모든 type을 순회하는 반복문을 작성하고 각각의 type이 가진 method수와 해당 Assembly이름을 표현하는 문을 아래와 같이 작성합니다.

Assembly? myApp = Assembly.GetEntryAssembly();

if (myApp is null) return;

foreach (AssemblyName name in myApp.GetReferencedAssemblies())
{
	Assembly a = Assembly.Load(name);

	int methodCount = 0;
	foreach (TypeInfo t in a.DefinedTypes)
		methodCount += t.GetMethods().Length;
 
	WriteLine("{0} Assembly : {1:N0} types -> {2:N0} methods",
		arg0: name.Name,
		arg1: a.DefinedTypes.Count(),
		arg2: methodCount);
}
N0은 대문자 N에 숫자 0을 붙인 것입니다. 즉, 대분자 N과 대문자 O가 아니며 소수점이 0인 숫자의 형식을 지정하는 것입니다.


Project를 실행합니다. 그러면 현재 OS상에서 Application이 동작할 때 사용하는 type과 method의 수를 확인할 수 있습니다. 다만 표시된 type의 method의 수는 실행되는 OS마다 약간이 차이가 있을 수 있습니다.

System.Runtime Assembly : 0 types -> 0 methods
System.Console Assembly : 44 types -> 654 methods
System.Linq Assembly : 110 types -> 1,348 methods
실행결과를 보면 System.Runtime assembly는 type의 수가 0 임을 알 수 있습니다. 해당 assembly는 실제 type을 가지기보다 type의 전달자만을 포함하고 있는 조금은 특별한 assembly입니다. type전달자는 .NET외부에서 또는 일부 다른 이유로 구현된 type을 나타냅니다.


다시 파일의 상단(System.Reflection namespace의 import이후)에 아래와 같이 import를 추가하고

using System.Net.Http;


바로 밑에 다음과 같은 변수선언을 추가합니다.

//추가적인 assembly의 type을 사용하는 변수를 선언합니다. 이 변수는 관련 type이 load되도록 하기 위한 것일뿐이므로 사용하지 않습니다.
System.Data.DataSet ds = new();
HttpClient client = new();


이 상태에서 project를 실행하면 아래와 같은 결과를 표시할 것입니다.

System.Runtime Assembly : 0 types -> 0 methods
System.Data.Common Assembly : 412 types -> 7,190 methods
System.Net.Http Assembly : 435 types -> 4,812 methods
System.Console Assembly : 44 types -> 654 methods
System.Linq Assembly : 110 types -> 1,348 methods

 

결과만 보면 C#을 배운다는 것은 위와 같은 많은 type과 method를 배워야 한다는 것으로 생각할 수 있겠지만 사실 이 모든 걸 배우고 기억해야 할 필요는 없습니다. C#을 조금 익히고 사용하다 보면 자주 사용되는 것은 따로 있음을 알게 되고 다른 type이나 method를 사용해야 한다면 그때 해당 부분만을 찾아보면 됩니다. Method는 단지 type이 가질 수 있는 member 중 하나의 범주에 해당할 뿐이며 다른 개발자들을 통해 새로운 type의 member들이 계속 정의되고 있기 때문에 이 모든 것을 안다는 것은 불가능합니다.

 

17) 자세히 들어야 보기


위에서 작성한 voca project의 code를 가지고 ChatGPT에게 설명을 요청해 보니 다음과 같은 응답이 돌아왔습니다.
"This C# program inspects the assemblies referenced by the current application and displays the number of types and methods in each referenced assembly."
위 설명을 토대로 좀 더 세부적으로 code를 살펴보면 다음과 같은 결론을 얻을 수 있습니다.

  • System.Reflection namespace는 실행 중에 assembly, module, type, method 등에 대한 작업에 사용됩니다.
  • System.Data.DataSet과 HttpClient instance를 선언하였지만 사용하지는 않았습니다. 다만 주석을 통해 각각에서 필요한 assembly가 하위 분석을 위해 load되로록 하기 위한 것이라는 설명을 덧붙이고 있습니다. 그런 뒤 해당 assembly에 대한 class의 instance를 생성함으로써 이들 assembly가 memory로 load 될 수 있도록 합니다.
  • Assembly.GetEntryAssembly()는 application주요 진입점에 해당하는 assembly를 가져옵니다.
  • myApp이 null(주요 assembly가 존재하지 않음을 의미하며 .NET App으로서 일바적인 동작이 불가능함)이라면 application을 종료합니다.
  • foreach loop는 주요 진업점에 해당하는 assembly에서 참조되는 각각의 assembly 이름을 열거합니다.
  • Assembly.Load(name)는 주어진 이름에 해당하는 assembly를 load 합니다. 이를 통해 해당 assembly와 상호작용을 수행할 수 있습니다.
  • methodCount는 선엄 됨과 동시에 0으로 초기화하고 있습니다. 그리고 assembly안에 있는 전체 method의 수를 확인하는 데 사용됩니다.
  • 또 다른 중첩된 foreach loop는 assembly안에서 각 type을 열거합니다.
  • 그리고 각 type에서 method의 수를 가져와 methodCount변수에 가산한 결과를 저장합니다.
  • 마지막으로 assembly안에 있는 type과 method의 수를 WriteLine() method를 사용해 console에 표시합니다. 이때 문자열에서 {0:N0} 형식지정자는 숫자(0은 상수 index를 참조하며, N은 숫자형식, 그리고 N다음에 다시 0은 소수점 자릿수를 지정합니다.)에 대한 자리표시자에 해당합니다.

이러한 동작방식을 통해 결과적으로 code는 application에서 참조되는 각각의 assembly에 얼마나 많은 type과 method가 존재하는지 확인하게 되는데, 이를 통해 application이 가진 의존성에 대한 규모와 복잡성을 이해하는데 조금은 도움이 될 수 있을 것입니다.

GitHub Copilot과 ChatGPT와 같은 도구는 새로운 것을 배울 때 꽤 유용한 도구가 될 수 있습니다. 자동적으로 code를 작성해 줄 뿐만 아니라 기존에 작성된 code를 설명해주기도 하고 심지어 더 나은 code를 제안받을 수도 있습니다.

 

4. 변수사용

 

모든 application은 data를 처리합니다. 여기서 data는 입력이 되고 처리되어 어딘가로 출력될 것입니다.


Data는 일반적으로 file이나 database, 사용자입력을 통해 program으로 들어오게 되며 이것을 program이 동작중인동 안 memory에 임시로 저장될 변수라는 곳에 넘겨질 수 있습니다. 이때 program이 종료되면 data 역시 memory에서 소거됩니다. 또한 Data는 file이나 database, 또는 화면이나 printer 등에 출력될 수 있습니다. 변수를 사용할 때는 우선 변수가 memory안에서 얼마나 많은 공간을 차지할지와 이것이 얼마나 빨리 처리될 수 있는지를 생각해 둘 필요가 있습니다.


개발자는 data를 적절한 type을 통해 가져와 data의 값을 사용하게 됩니다. 이때 int나 double과 같은 단순한 일반적인 type은 크기가 다른 저장 box로 생각할 수 있습니다. 저장 box가 작을수록 더 작은 memory를 차지하게 되지만 그렇다고 더 빨리 처리되는 것은 아닙니다. 예를 들어 16bit 수에 대한 덧셈 처리가 64bit system에서 64bit에 대한 덧셈처리보다 더 빨라지는 것은 아닙니다. type에 따라 이들 box 중 일부는 근접히 쌓이는 형태가 될 수 있고 일부는 멀리 큰 heap영역에 보내질 수 있습니다. 지금은 어렵게 들리겠지만 이제 천천히 나아가면서 하나씩 이해하게 될 것입니다.

 

1) 변수에 이름을 붙이고 값할당하기


변수에 이름을 짓는 것에도 규칙이 존재합니다. 물론 강제사항은 아니지만 가급적 이러한 규칙을 준수하는 것이 좋습니다.

규칙 에제 적용범위
Camel case user, userName, dateOfBirth 지역변수, private field
Title case(Pascal case) User, UserName, DateOfBirth Type, method와 같은 member 및 private field


일부 C#개발자들은 private field의 경우 '_userName'처럼 _(underscore)를 붙여 변수명만으로 다른 일반적인 변수와 구별하는 경우가 있습니다. 이외 다른 private member는 외부에서는 노출되는 것이 아니므로 이들에 대한 naming규칙은 공식적으로 정의된 바 없습니다. 따라서 이들 변수에 _문자를 접두사로 붙이는 것은 선택사항입니다.

어떤 형태로든 일관성 있는 naming규칙을 적용하면 다른 개발자뿐만 아니라 미래에 자신이 code를 보기에도 훨씬 편해질 수 있습니다.


아래 code는 명명된 지역 변수와 여기에 = 문자를 통해 값을 할당하는 방법을 나타내고 있습니다. 또한 C# 6에서 도입된 nameof keyword를 사용하면 변수의 이름자체를 다룰 수 있습니다.

int userAge = 30;
Console.WriteLine($"variable {nameof(userAge)} value : {userAge}");
C# 12부터 nameof는 정적 context에서 instance data에 access 할 수 있습니다. 추후에는 instance와 정적 data의 차이점에 대해 자세히 알아볼 것입니다.

 

2) Literal 값


변수에 값을 할당할 때 항상 그런 것은 아니지만 대게는 literal값을 할당합니다. literal은 고정된 값을 나타내는 표기법으로서 data type마다 그들에 대한 literal값을 나타내는 표기법이 조금씩 달라집니다. 이에 대한 내용은 향후 진행할 때마다 변수에 literal값을 할당하는 여러 예제를 보면서 그 의미를 파악하실 수 있을 것입니다.

아래 link를 통해 C#언어 명세에서 literal에 관해 공식적으로 정의한 내용을 살펴볼 수 있습니다.
https://learn.microsoft.com/en-us/dotnet/csharp/language-reference/language-specification/lexical-structure#645-literals

 

3) text 다루기


'A'와 같은 단일 문자는 char type을 통해 저장됩니다.

겉으로는 단순하지만 실제로는 조금 더 복잡해질 수 있습니다. 예를 들어 Egyptian 상형문자는 A002(U+13001)는 이를 표현하기 위해 2개의 System.Char값(surrogate pairs)을 필요로 합니다(\uD80C, \uDC01). 이러한 사실은 특별한 경우 code에서 bug를 찾기가 매우 어려워질 수 있는 상황을 만들 수 있으므로 하나의 char가 항상 하나의 문자에 대응하지 않을 수 있다는 점을 알고 있어야 합니다.


char는 홀따옴표(')를 사용해 할당할 literal값을 둘러싸서 할당합니다. 아니면 아래 예제에서처럼 함수의 결과로 반환되는 값을 할당할 수도 있습니다.

char letter = 'A';
char one = 'B';
char result = GetChar();


반면 'car'나 'airplan'과 같은 문자열은 string type으로 저장되며 할당할 literal값을 큰따옴표(")로 둘러싸거나 생성자 혹은 함수의 반환값으로 할당할 수 있습니다.

string name = "kim";
string youName = "Hong";
string result = GetGroupMembers();
string autoValue = new('-', 10); //hypen 10개

 

(1) emoji 표현하기


Windows의 Terminal을 사용하면 command prompt상에서 emoji를 표시할 수 있습니다.(이전 Prompt에서는 emoji를 지원하지 않기 때문에 사용할 수 없습니다.) 이를 사용하기 위해서는 우선 UTF-8로 console의 표시 encoding을 설정해야 하며 그다음 ConvertFromUtf32 method를 사용해 원하는 emoji를 표시합니다.

Console.OutputEncoding = System.Text.Encoding.UTF8;
string myEmoji = char.ConvertFromUtf32(0x1F600);
Console.WriteLine(myEmoji);


(2) 축자 문자열(Verbatim strings)


확장 문자열(escape character)은 programming에서 특별한 문자로 취급되며 escape sequence를 사용하는 문자열을 처리합니다. 여기서 escape sequence는 문자열에서 직접적으로 포함시키기 어렵거나 불가능한 문자를 표현하기 위한 것으로, 대부분 \(Backslash) 문자로 시작하여 하나 또는 그 이상의 문자열을 사용함으로써 표현합니다.


문자열 변수에서 문자열을 저장할 때는 바로 이런 \를 사용하여 개행이나 tab과 같은 특수한 문자열을 나타내는 escape sequence를 아래와 같이 포함시킬 수 있습니다.

string s = "abc\tdefg";


그런데 만약 Windows상에서 file에 대한 경로를 표시할 때 folder 중 하나가 T로 시작한다면 어떻게 처리될까?

string path = "C:\tmp\files\log.txt";


결론부터 얘기하면 위와 같은 경우 compiler는 \t를 tab문자로 변환하기 때문에 오류를 일으키게 됩니다.


이런 문제를 해결하려면 @문자를 접두사로 붙여야 하며 이때 이렇게 완성된 문자열을 'verbatim literal string'이라고 합니다.

string path = @"C:\tmp\files\log.txt";


참고로 C# 13부터는 \e라는 escape sequence를 사용해 ESC문자(Unicode U+001B)를 표현할 수 있습니다.

char esc = '\e';
char esc = '\u001b';


\u001b는 이전에도 사용가능한 방식이며 간혹 \x1b를 사용한 경우도 있는데 \x1b에서 1b다음에 오는 16진수 숫자는 escape sequence의 일부로 부정확하게 해석될 수 있으므로 권장하지 않습니다.

 

(3) 원시 문자열 literal


C# 11에서 도입된 것으로 content에 임의의 처리를 하지 않고도 문자열을 있는 그대로 표현할 수 있는 편리한 방법입니다. 단순 문자열은 물론이고 XML이나 HTML, JSON과 같은 format의 문자열(또는 이런 format을 포함하는)의 literal 또한 쉽게 정의할 수 있습니다.


원시 문자열 literal은 아래와 같이 3개나 또는 그 이상의 쌍점으로 시작하고 끝나는 형태를 갖습니다.

string sHtml = """
    <html>
        <head lang = "ko">
            <title></title>
        </head >
        <body>
        </body>
    </html>
    """;


원시 문자열 literal이 3개나 그 이상의 쌍점을 사용하는 이유는 content자체가 3개 이상의 쌍점을 포함해야 하는 경우가 있을 수 있기 때문입니다. 만약 content내부에서 3개의 쌍점을 사용한다면 원시 문자열 literal은 4개 이상을 사용해야 합니다. 즉, content가 포함하고 있는 쌍점 이상의 쌍점으로 원시 문자열 literal을 표현해야 한다는 뜻입니다.


상기 code에서 HTML을 표현하는 문자열은 '<html>'문자열이 시작하는 지점부터 들여 쓰기를 적용하고 있는데 compiler가 이 code를 해석할때는 마지막 쌍점 3개를 기준으로 문자열에 대한 모든 들여쓰기를 적용하게 됩니다. 그 결과 code상에서 사용한 들여 쓰기는 적용되지 않으며 결과적으로 왼쪽으로 정렬된 상태를 보이게 됩니다.

<html>
    <head lang = "ko">
        <title></title>
    </head >
    <body>
    </body>
</html>


따라서 만약 아래와 같이 왼쪽 가장자리로 3개 쌍점을 사용하게 된다면

string sHtml = """
    <html>
        <head lang = "ko">
            <title></title>
        </head >
        <body>
        </body>
    </html>
""";


content는 그만큼의 공백으로 content를 표현할 것입니다.

    <html>
        <head lang = "ko">
            <title></title>
        </head >
        <body>
        </body>
    </html>


(4) 원시 보간문자열 literal


원시 문자열 literal을 사용할 때는 중괄호({})를 사용하여 보간문자열을 혼용해 사용할 수도 있습니다. 이렇게 하려면 literal을 시작할 때 실제 내용으로 교체될 표현식을 나타내는 괄호의 수를 생각하고 $문자를 해당 괄호의 수만큼 추가하여 시작함으로써 보간문자열로 사용할 중괄호를 인식하게 해야 합니다. 왜냐하면 처리될 괄호의 수보다 더 적은 모든 괄호는 실제 원시 content로 처리되기 때문입니다.


예를 들어 다음과 같이 JSON을 문자열을 사용하고자 하는 경우 하나의 단일 괄호는 일반적인 괄호로 처리되지만 2개의 $문자를 사용함으로써 compiler는 모든 2개의 중괄호를 표현식의 값으로 변경하도록 시도할 수 있습니다.

string name = "kim";
int age = 30;

string personJson = $$"""
    {
        "name": "{{name}}",
        'age': {{age}},
        'city': "{{{"New York"}}}"
    }
    """;

Console.WriteLine(personJson);


위 실행결과는 다음과 같습니다.

{
    "name": "kim",
    'age': 30,
    'city': "{New York}"
}

 

즉 위에서 사용한 $의 수는 compiler에게 얼마만큼의 중괄호를 보간문자열로 인식하는 데 사용할 것인가를 말해주는 것입니다.

또한 위 예제에서는 중괄호를 3개 사용하는 부분도 있는데 이 것은 실수가 아닙니다. 정확히 중괄호 자체를 결과에 포함시키기 위한 것인데, 이런 경우 내부 괄호 처음 1개는 일반적인 괄호로 인식되고 다음 2개의 중괄호가 보간문자열로 인식됩니다. 문자열에서는 이전 괄호 2개를 통해 보간문자열로 인식을 시작하였으므로 이후에 나오는 닫는 중괄호 2개가 자연스럽게 보간문자열로 인식되고 그다음 나오는 1개의 중괄호가 일반적인 중괄호로 취급되기 때문에 보간 문자열을 통한 'New York'이라는 문자열과 일반적인 문자로 인식한 중괄호를 포함하여 출력의 결과로 '{New York}'문자열을 생성하게 됩니다.


따라서 만약 '"{{{"New York"}}}"'부분을 '"{{"New York"}}"'으로 변경하게 되면 결과에서 더 이상 중괄호는 나타나지 않게 됩니다.

 

(5) 문자열 요약

  • Literal 문자열 : 큰따옴표로 표현된 문자열을 의미하며, 여기에서 tab을 의미하는 \t처럼 확장문자열을 사용할 수 있습니다. 이때 backslash자체를 표현하려면 backslash를 두 번 사용합니다.
  • Raw 문자열 literal : 3개 또는 그 이상의 큰따옴표로 표현된 문자열입니다.
  • Verbatim 문자열 : @접두사가 붙은 literal 문자열로서 확장문자열을 무효화하여 backslash를 있는 그대로 사용할 수 있게 합니다. 또한 공백문자 역시 compiler가 별도로 처리하지 않고 있는 그대로 취급함으로써 여러 줄에 걸친 문자열생성도 가능합니다.
  • 보간 문자열 : $접두사가 붙은 literal 문자열로서 문자열자체에서 변수와 같은 요소를 포함시킬 수 있습니다.

4) 숫자 다루기

 

숫자는 덧셈과 같은 산술연산처리를 수행하기 위한 data라고 할 수 있습니다. 따라서 전화번호는 수에 해당하지 않습니다. 변수를 선언할 때 type을 숫자형식에 맞춰야 할지 고민된다면 해당 변숫값을 산술연산에 사용해야 하는지, 또는 (02)-1234-5678처럼 괄호나 hyphen이 사용되어 숫자를 형식해야 하는지 여부를 스스로 따져보면 됩니다. 전화번호의 경우는 숫자가 아닌 문자열로서 취급되어야 합니다.

 

숫자는 37과 같이 셈 이 가능한 자연수가 될 수 있으며 이때 37과 같은 숫자를 범자연수라고 표현하기도 합니다. 또한 -37과 같은 수는 자연수라고 하며 3.7과 같은 실수도 숫자가 될 수 있습니다. 참고로 이러한 수는 computer에서 다뤄질대 단정밀도 또는 배정밀도 부동 소수점이라고 합니다.


숫자와 관련된 code를 간단히 작성해 보기 위해 Numbers라는 이름의 console project를 생성합니다. 그리고 project의 Program.cs에서 기존의 구문을 모두 삭제한 다음 아래와 같이 숫자와 관련된 몇 가지 type의 변수를 선언합니다.

int age = 30;
uint old = 300;
float f = 3.7f;
double d = 3.7;


예제에서 uint는 무부호정수(unsigned integer)라고 하며 양수나 0만을 가지는 type입니다. 반면 int는 정수인데 음수나 양수 또는 0의 값을 가질 수 있습니다. uint는 음수는 표현할 수 없지만 그만큼 int보다 더 많은 양수값을 가질 수 있습니다.

 

(1) 자연수 다루기


특별한 경우를 제외하면 우리가 알고 있는 일반적인 computer들은 거의 다 data를 bit로 저장하며 이를 이진수(binary number)라고 합니다. 반면 사람(인간)의 경우에는 10진수(decimal number)를 사용합니다.


10진수는 흔히 'Base 10'으로 표시하기도 하는데 이는 0부터 9까지 10개의 숫자를 가진다는 것을 의미합니다. 10진수는 대부분의 사람들이 사용하는 가장 일반적인 방식이지만 다른 방식 역시 과학이나 기술분야 및 전산분야에서 많이 사용되고 있습니다. 이 중 2진수가 많이 사용되는데 이는 Base 2라고 하며 0과 1만을 가진 숫자체계임을 의미합니다.

 

아래 표는 computer가 10진수를 어떤 방식으로 저장하는지를 나타내고 있습니다. 그림에서 8과 2의 값이 1 임에 주목하세요. 1인 것만 계산해 보면 8 + 2가 되므로 결과는 10 됩니다.

128 64 32 16 8 4 2 1
0 0 0 0 1 0 0 1

 

다시 말해 10진수 10은 이진수(8bit = 1byte)로 00001010이 됩니다.

 

① 구분자를 사용해 가독성 향상하기


C# 7부터는 숫자에 구분자를 사용할 수 있습니다. 이 구분자는 _(underscore)로서 이를 통해 숫자를 특정 단위로 분리할 수 있으며 특히 binary를 표현하는데 유용하게 사용될 수 있습니다.


_문자는 decimal, binary, 16진수를 포함해 거의 모든 숫자 literal에서 어디든 삽입하여 가독성을 향상할 수 있습니다. 예를 들어 10진수 표기법으로 1백만 값을 표현하는 경우 '1000000'보다는 '1_000_000'과 같이 표현하는 것이 훨씬 읽기 쉬울 것입니다.

② binary 또는 16진수 표기법 사용하기


0과 1만을 사용하는 Base 2(binary)를 literal로 표현하려면 0b로 시작해야 하며 16진수(Base 16, 0~9 및 A~F사용)인 경우 0x로 시작해야 합니다.

③ 범자연수 예제


이전에 생성한 Numbers project의 Program.cs에서 int형 변수를 선언하고 _를 사용해 값을 아래와 같이 할당합니다.

int _decimal = 1_000_000;
int _binary = 0b_0001_0011_1000;
int _hexadecimal = 0x_001A_FF84;

// 값을 10진수로 표현하기
Console.WriteLine($"{_decimal:N0}");
Console.WriteLine($"{_binary:N0}");
Console.WriteLine($"{_hexadecimal:N0}");

// 값을 16진수로 표현하기
Console.WriteLine($"{_decimal:X}");
Console.WriteLine($"{_binary:X}");
Console.WriteLine($"{_hexadecimal:X}");


참고로 Computer는 int형이나 사촌 격인 long, short와 같은 type의 값은 오차 없이 정확하게 표현할 수 있습니다.

 

(2) 실수 다루기


Computer는 실제 숫자 즉, 10진수 혹은 정수가 아닌 수를 정확하게 표현할 수 없습니다. 실수의 경우 float과 double type은 단정도 그리고 배정도 부동 소수점을 사용해 저장하는데 대부분의 Programming언어에서는 부동소수점 계산을 위해 Institute of Electrical and Electronics Engineers (IEEE)을 구현하고 있으며 이때 IEEE 754는 1985년 IEEE에서 수립된 부동 소수점 계산에 관한 기술표준에 해당합니다.


아래 이미지는 binary표기법을 통해 computer가 12.75라는 숫자를 어떻게 표현할 수 있는지를 간단히 설명하고 있습니다. 이미지에서 8, 4 그리고 ½과 ¼의 값이 1 임에 주목하시기 바랍니다.

8+4+½+¼ = 12¾ = 12.75


따라서 10진수 표시값인 12.75는 binary 표기법으로 00001100.1100이 되는데 보시는 바와 같이 12.75라는 값은 이러한 bit를 사용해 정확하게 표시할 수 있습니다. 그러나 문제는 대부분의 숫자가 꼭 이렇지만은 않다는 것인데 이에 관해서는 곧 살펴볼 것입니다.

① 숫자 크기를 확인하기 위한 code작성하기


C#에서는 특정 숫자 type이 Momory를 사용하는 크기의 값을 byte로 반환하는 sizeof()라는 연산자를 가지고 있으며 또한 일부 number type에서는 MinValue와 MaxValue라는 member를 가지고 있어서 이를 통해 type의 변수에 저장될 수 있는 최소와 최댓값을 확인 할 수 있습니다. 지금 작성해 볼 예제에서는 이러한 기능을 사용한 Console app을 생성하고 각 type의 크기를 확인해 볼 것입니다.


Numbers Project의 Program.cs에서 기존의 구문을 모두 지우고 file의 하단에 아래 구문을 작성합니다. 해당 code는 3개의 number type에 대한 data type크기를 표시합니다.

Console.WriteLine($"int가 사용하는 크기는 {sizeof(int)} byte 이며 {int.MinValue:N0} 에서 {int.MaxValue:N0} 까지 저장가능");
Console.WriteLine($"double이 사용하는 크기는 {sizeof(double)} byte 이며 {double.MinValue:N0} 에서 {double.MaxValue:N0} 까지 저장가능");
Console.WriteLine($"decimal이 사용하는 크기는 {sizeof(decimal)} byte 이며 {decimal.MinValue:N0} 에셔 {decimal.MaxValue:N0} 까지 저장가능");


위 예제를 실행하면 다음과 같은 결과를 표시할 것입니다.


int 변수는 memory에서 4byte를 사용하며 양수 또는 음수를 최대 20억까지 저장할 수 있습니다. double 변수는 8byte의 memory를 사용하며 위 결과이미지와 같이 엄청난 값을 저장할 수 있습니다. decimal 변수는 16byte를 사용하며 double만큼은 아니지만 비교적 큰 수를 저장할 수 있습니다.


여기까지 보면 'decimal이 더 큰 memory를 차지함에도 불구하고 왜 double이 훨씬 더 많은 값을 저장할 수 있을까?'라는 의문을 가질 수 있습니다. 이에 대한 답을 바로 알아보도록 하겠습니다.

 

② double과 decimal


double과 decimal의 비교를 위해 아래와 같은 예제를 작성합니다. 아직까지는 예제의 구문에 대한 모든 이해를 필요로 하는 것이 아니므로 code의 의미를 알지 못하더라도 걱정할 필요 없습니다.


예제는 2개의 double변수를 선언하고 있으며 이들의 값을 더한 후 그 결과치를 예상값과 비교하고 있습니다. 그런 다음 비교 후의 결과를 console에 표시합니다.

double a = 0.1;
double b = 0.2;

if (a + b == 0.3)
{
    Console.WriteLine($"{a} + {b} = {0.3}");
}
else
{
    Console.WriteLine($"{a} + {b} ≠ {0.3}");
}


위 에제를 실행하면 결과를 다음과 같을 것입니다.

문화권에 따라서 소수분리를 위해 ,(comma)를 사용하는 경우도 있습니다.


double유형은 정확성을 보증하지 않습니다. 예제처럼 0.1, 0.2와 같은 수는 부동소수점값을 문자 그대로 정확하게 표현하지 않기 때문입니다.


예제에서는 결과가 false가 되었지만 0.1 + 0.3 == 0.4와 같이 비교하게 되면 true를 반환할 수도 있습니다. 일부 부정확한 값은 실제 수학적으로 같지 않아도 현재 표현상태로는 정확히 같다는 결과를 낼 수 있습니다. 애매하게도 어떤 건 직접적으로 비교될 수 있지만 어떤 것은 그렇게 될 수 없습니다. 결론적으로 double을 사용해 정확한 비교결과를 얻으려고 하는 것은 위험할 수 있습니다.


실수는 float을 사용하여 비교할 수 있습니다. double과는 정확성이 떨어지지만 오히려 이러한 특징 때문에 값의 대한 비교를 일관성 있게 수행합니다.

float a = 0.1F;
float b = 0.2F;

if (a + b == 0.3F)

 

대략적인 정확도만으로 괜찮은 경우에만 double을 사용합니다. 특히 두 숫자와 동등성을 비교하면서 정확도가 중요하지 않은 경우인데, 예를 들어 어떤 사람의 키를 측정하는 경우 더 크거나 작은 지정도만 비교하고, 같음으로 비교하지는 않은 경우입니다.


위 예제의 문제점은 computer가 어떻게 0.1이나 그 배수의 값을 저장하는지로 설명될 수 있습니다. 2진수로 0.1을 나타내기 위해 computer는 1/16, 1/32, 1/256, 1/512... column처럼 계속해서 1을 저장하는 구조를 갖게 됩니다.


즉, 10진수 0.1은 2진수로 0.00011001100110011...처럼 끝없이 반복되게 됩니다.

double을 '=='연산자를 통해 비교하지 마십시오. 이에 대한 유명한 사례로 1990년부터 1991까지 이어진 Gulf전에서 미군 Patriot missile battery의 계산방식을 들 수 있습니다. 실제 정확성에서 오차가 발생하여 공격해 오는 Iraqi Scud missile의 추적에 실패하였고 이 때문에 수십 명의 미군사상자가 발생하였습니다.


이제 위 예제를 다시 decimal로 변경하여 작성합니다.

decimal a = 0.1M;
decimal b = 0.2M;

if (a + b == 0.3M)
{
    Console.WriteLine($"{a} + {b} = {0.3}");
}
else
{
    Console.WriteLine($"{a} + {b} ≠ {0.3}");
}


위 code를 실행하면 다음과 같은 결과가 표시될 것입니다.

 

decimal의 경우 숫자를 큰 정수로 저장한 다음 소수점을 이동시키는 방식을 사용하기 때문에 더 정확한 비교가 가능합니다. 예를 들어 12.75라는 값이 있다면 이걸 decimal에서는 1275로 저장하고 소수점을 왼쪽으로 2자리 이동시키는 것입니다.

특별한 경우가 아니라면 정수에서는 int를 사용하고 다른 값과의 동등성을 비교하지 않고 더 크거나 작다는 비교만 하는 실수의 경우에는 double을 사용합니다. 통화나 CAD 설계, engineering처럼 정확성이 중요한 부분에서는 decimal을 사용합니다.

 

③ 특수한 실수 값


float과 double type에서는 일부 유용하게 사용할 수 있는 특별한 실수값을 가지고 있습니다. 그중 NaN은 Not a Numner로서 0 나누기 0과 같은 경우를 의미하며, Epsilon은 float이나 double에서 저장가능한 가장 작은 값을, PositiveInfinity와 NegativeInfinity는 무한히 큰 음수와 양수값을 나타냅니다. 또한 이러한 특정값을 확인하기 위한 method도 사용할 수 있습니다.


아래 code에서는 이러한 특수값을 표현한 예시를 확인할 수 있습니다.

Console.WriteLine($"double.Epsilon: {double.Epsilon}");
Console.WriteLine($"double.Epsilon to 324 decimal places: {double.Epsilon:N324}");
Console.WriteLine($"double.Epsilon to 330 decimal places: {double.Epsilon:N330}");
Console.WriteLine();
Console.WriteLine($"double.NaN : {double.NaN}");
Console.WriteLine($"double.PositiveInfinity : {double.PositiveInfinity}");
Console.WriteLine($"double.NegativeInfinity : {double.NegativeInfinity}");

 

위 code를 실행하면 아래와 같은 결과를 표시할 것입니다.

 

다시 정리하자면

  • NaN은 not a number를 의미하는 것으로 0을 0으로 나눌 때와 같은 경우에 생성될 수 있는 값입니다.
  • PositiveInfinity는 옆으로 누운 8 자 모양으로 표현되며 양의 실수를 0으로 나눈 식에서 생성될 수 있습니다.
  • NegativeInfinity는 옆으로 누운 -를 가진 8자 모양으로 표현되며 음의 실수를 0으로 나눈 식으로 생성될 수 있습니다.
  • 양의 실수에 의해 나누어진 0은 0입니다.
  • 음의 실수에 의해 나누어진 0은 0입니다.
  • Epsilon는 scientific 표기법을 사용하여 표현되며 5E-324보다 약간 더 작습니다.

5) 새로운 number type과 비안전 code(unsafe)


System.Half type은 .NET5에서 도입된 것으로 float이나 double처럼 실수로 저장될 수 있으며 memory에서 일반적으로 2byte를 사용합니다. System.Int128과 System.UInt128 type은 .NET7에서 도입된 것으로 int와 uint처럼 부호(양수 및 음수)와 부호 없는(0과 양수) 정수값을 저장할 수 있으며 memory에서 16byte를 사용합니다.


이들 새로운 number type에 대해서 sizeof연산자를 사용할 때는 unsafe code block안에서만 사용하여야 하며 compile시에도 project설정에서 unsafe code를 사용하도록 해야 합니다.


이에 대한 예제를 작성하기 위해 Program.cs에서 아래 구문을 작성합니다. 이 예제 code는 Half와 Int128 data type의 size를 확인하도록 합니다.

unsafe
{
    Console.WriteLine($"Half -> {sizeof(Half)} bytes({Half.MinValue:N0} to {Half.MaxValue:N0})");
    Console.WriteLine($"Int128 -> {sizeof(Int128)} bytes({Int128.MinValue:N0} to {Int128.MaxValue:N0})");
}


그리고 Numbers.csproj에서 unsafe code를 사용하기 위한 요소를 아래와 같이 추가합니다.

<Project Sdk="Microsoft.NET.Sdk">
  <PropertyGroup>
    <OutputType>Exe</OutputType>
    <TargetFramework>net9.0</TargetFramework>
    <ImplicitUsings>enable</ImplicitUsings>
    <Nullable>enable</Nullable>
    <AllowUnsafeBlocks>True</AllowUnsafeBlocks>
  </PropertyGroup>
</Project>


code를 실행하면 아래와 같은 결과를 표시할 것입니다.

sizeof연산자는 int와 byte같이 일반적으로 사용되는 것들을 제외하고는 unsafe block code안에서 사용되어야 합니다. 좀 더 자세한 사항은 아래 link를 참고하시기 바랍니다.
https://learn.microsoft.com/en-us/dotnet/csharp/language-reference/operators/sizeof
또한 unsafe code는 검증된 안정성을 가질 수 없다는 점의 주의해야 합니다.
https://learn.microsoft.com/en-us/dotnet/csharp/language-reference/unsafe-code

 

6) Boolean 다루기

Boolean type은 true 또는 false 중 하나의 값만 가지는 것으로서 아래와 같이 사용됩니다.

bool b1 = true;
bool b2 = false;

대게 Boolean은 분기나 loop에 많이 사용되는 것으로 분기나 loop에 관해서는 흐름제어를 다루는 부분에서 자세히 알아볼 것입니다.

7) object 다루기

object라는 것은 어떠한 유형의 type도 저장할 수 있는 다소 특별한 type입니다. 하지만 code가 지저분해질 뿐만 아니라 성능에도 부정적인 영향이 발생할 수 있으므로 가능한 한 object사용을 피하는 것이 좋습니다. 그러나 간혹 object를 아예 피할 수 없는 경우도 있는데 third-party나 심지어 microsoft의 library를 사용하는 어떤 경우에는 불가피하게 object사용을 필요로 할 수 있습니다.


object사용에 대한 약간의 code를 만들어보기 위해 Study-02에서 Variables라는 console project를 생성합니다. 그런 다음 Program.cs에서 기존의 구문을 모두 삭제하고 다음과 같이 object type의 변수를 선언하고 이를 사용하는 아래 에제를 작성합니다.

object o1 = 123;
object o2 = "abc";
Console.WriteLine($"{o1} --- {o2}");

int i1 = o2.Length;
int i2 = ((string)o2).Length;
Console.WriteLine($"{i2}");


상기 예제를 실행하려고 하면 o2변수의 type을 compiler가 알지 못하기 때문에 compile error가 발생할 것입니다. 이제 'int i2 = ((string)o2).length;'이 부분을 주석처리하고 해당 code가 compile 되지 않도록 합니다.


다시 에제를 실행하면 이번에는 성공적으로 실행될 것입니다. i1변수 할당 부분과 달리 ((string)o2).Length;에서는 개발자가 o2변수 앞에 (string)과 같이 어떤 type으로 변환할지에 대한 형변환시도를 하고 있고 이로 인해 compiler가 o2변수를 string형식으로 판단할 수 있기 때문입니다. 이렇게 변환된 string type은 Length라는 속성을 갖고 있으므로 구문상 아무런 문제가 없고 그 결과 아래와 같이 예제의 실행결과를 볼 수 있습니다.

형변환에 관해서는 추후에 자세히 알아볼 것입니다.

 

object type은 처음 C#이 등장할 때부터 사용가능했던 것으로 위 예제에서 처럼 형변환과정에서 성능상의 이슈가 발생하는 문제점이 있습니다. 이러한 object의 문제점을 보완하고자 C# 2.0 이후부터 generic을 도입하였는데, generic에 관해서는 나중에 살펴볼 기회가 있을 테지만 간단히 말하면 성능상의 부하 없이 type에 대한 유연성을 제공하는 기능입니다.

 

결론적으로 말하면 object 혹은 이와 연결되는 System.Object 사용을 되도록 자제하고 generic과 같은 대안을 사용하는 것이 좋습니다.

 

8) dynmic 다루기

dynamic은 C# 4.0에서 도입된 것으로 object처럼 모든 유형의 data를 담을 수 있습니다. 그런데 object와는 달리 dynamic변수에

담긴 data type은 형변환 없이도 고유의 member를 호출할 수 있습니다.

dynamic dm;
dm = new[] { 1, 2, 3};
dm = 12;
dm = "abc";
Console.WriteLine($"{dm.Length}");
Console.WriteLine($"{dm.GetType()}");
'new[]'는 배열을 의미합니다. 배열에 관해서는 추후에 자세히 알아볼 것입니다.


code를 compile 하고 실행하면 아래와 같은 결과를 표시할 것입니다. code상으로 dm변수는 마지막에 문자열을 할당하고 있으므로 Length속성을 호출할 수 있습니다.

 

문자열을 할당하는 부분에서 //로 주석을 처리하고 다시 예제를 실행합니다. 예제상 dm에 할당되는 값은 int형이고 int형은 Length속성이 존재하지 않으므로 오류가 발생할 것입니다.


이제 다시 int형 값을 할당하는 부분도 //로 주석처리하고 예제를 재실행합니다. array의 경우에는 Length속성을 가지고 있기 때문에 이번에는 오류가 나지 않고 다음과 같은 결과를 출력할 것입니다.

 

다만 dynamic을 사용하면 code editor상에서 IntelliSense를 사용할 수 없게 됩니다. dynamic은 compiler가 build 하는 동안에는 해당 type을 확인할 수 없기 때문이며, 대신 CLR(Common Language Runtime)이 runtime에서 member를 확인하고 만약 잘못 사용된 것이 있다면 예외를 일으키게 됩니다.

예외라는 것은 runtime에서 무엇인가 잘못되었음을 표시하는 방식인데 예외라는 것이 무엇인지, 그리고 이를 어떻게 제어할 수 있는지에 대해서는 추후에 자세히 알아볼 것입니다.

dynamic은 주로 비 .NET system과의 상호작용을 위해 사용되는 경우가 많은데, 예를 들어 F#이나 Python, JavaScript 등으로 작성된 class library를 사용해야 하는 경우와 Excel이나 Word에서의 추가기능에서 COM(Component Object Model)과 같은 기술과의 상호작용등에 사용될 수 있습니다.

9) 지역변수

지역변수는 method안에서 선언되는 변수를 의미합니다. 따라서 method가 실행되는 동안에만 존재할 수 있으며 method 실행이 끝나면(method가 결괏값을 반환하면) 지역변수를 위해 할당된 memory는 소거됩니다.

엄밀히 말하면 값 type의 경우 소거되지만 참조 type의 경우에는 garbage collection에 의해 처리되기를 대기해야 합니다. 추후에는 값 type과 참조 type에 대한 내용과 비관리 Resource를 해제할 때 한 번의 garbage collection으로 resource를 해제할 수 있는 방법에 관해 알아볼 것입니다.

 

(1) 지역변수 type 명시하기

아래 예제에서는 특정 type으로 몇 가지 지역변수를 선언하고 있습니다.

int i = 100;
double d = 0.8;
decimal m = 1.88M;
string s = "abc";
char c = 'c';
bool b = false;


실제 위 code를 작성하고 나면 현재 사용 중인 code editor와 color scheme에 따라 변수에 값이 할당되었지만 사용되지 않는다는 것을 경고하기 위해 녹색 물결 밑줄선이 각 변수명에 나타날 수 있습니다.

(2) 지역변수의 type 추론하기

C# 3부터는 지역변수를 선언할 때 var keyword를 사용할 수 있습니다. 그러면 compiler는 할당연산자(=) 이후에 할당한 값을 통해 type을 자동으로 추론하게 됩니다. 이러한 동작은 compile시에 발생하므로 실제 var keyword자체가 runtime성능에 부정적인 영향을 주지는 않습니다.

소수점이 없는 숫자는 기본적으로 int로 추론되지만 아래의 접미사를 값에 사용하게 되면 해당 접미사에 따른 각각의 type을 추론하게 됩니다.

접미사 추론유형
L long
UL ulong
M decimal
D double
F float


소수점이 있는 숫자의 경우 아무런 접미사도 사용되지 않으면 기본적으로 double type으로 추론됩니다.

값에 큰따옴표를 사용하면 문자열 type이 되며 홑따옴표를 사용하면 문자 type이 됩니다. 또한 true나 false는 bool type으로 추론됩니다.

위 예제에 var keyword를 사용하면 아래와 같이 code가 만들어질 수 있습니다.

var i = 100;
var d = 0.8;
var m = 1.88M;
var s = "abc";
var c = 'c';
var b = false;


특별한 type을 명시하지 않더라도 각각의 var keyword에 mouse pointer를 올려두면 VS Code나 Visual Studio에서 tooltip을 통해 추론된 type이 표시됩니다.

Program.cs에서 상단에 아래와 같이 using문을 추가하면

using System.Xml;


해당 namespace를 import 하게 되는데 위와 같은 경우 이를 통해 XML type을 사용하는 몇 가지 변수를 선언할 수 있게 됩니다.

var xml1 = new XmlDocument();
XmlDocument xml2 = new XmlDocument();

var file1 = File.CreateText("sample.txt");
StreamWriter file2 = File.CreateText("sample.txt");

var keyword가 꽤 쓸모 있기는 하지만 var를 사용하지 않으려는 일부 개발자들도 존재합니다. 너무 남발하면 code를 읽어야 하는 누군가의 입장에서 이해하기 힘든 code가 될 수 있다는 이유에서 입니다. 그래서 var는 type이 분명한 경우에만 사용해야 한다는 의견이 있습니다. 예를 들어 위 에제에서 첫 번째 구문은 xml 변수의 type이 무엇인지를 명시하는 두 번째 구문만큼 명확하면서도 더 짧게 작성되었습니다. 하지만 3번째 구문은 file변수의 type이 무엇인지 명확하지 않은데, 때문에 type이 StreamWriter이라는 것을 명시하고 있는 4번째 구문이 오히려 가독성 측면에서 더 나은 방법일 수 있습니다.

 

var는 project를 build 할 때 compiler에 의해 실제 type으로 변환됩니다. 즉, var를 사용해 선언된 변수는 이미 특정한 type이 정해진다는 것을 의미하며 compiler에 의해 아무것도 변환하지 않는 dynamic과는 차이가 있습니다. 실제 dynamic은 System.Dynamic type으로 유지되며 모든 data type의 모든 개체를 참조하고 runtime시에 실제 type을 확인합니다. 따라서 존재하지 않는 member의 접근을 시도하거나 type에 맞지 않는 잘못된 연산을 수행하게 되면 예외가 발생하게 됩니다.

Visual Studio의 Refactoring기능을 사용하면 var를 실제 type으로 자동적으로 변환시킬 수 있습니다. 자세한 사항은 아래 link를 참고하시기 바랍니다.
https://learn.microsoft.com/en-us/visualstudio/ide/reference/convert-var-to-explicit-type?view=vs-2022

 

10) new의 이해

지금까지 작성한 몇 가지 예제에서 별언급을 하지 않았지만 new라는 keyword를 종종 사용해 왔습니다. new keyword의 역할은 간단히 말해 memory의 할당/초기화하는 역할을 수행하는 것인데 new의 필요성을 이해하기 위해 type에 대한 약간의 설명을 더 해야 할 것 같습니다.

값과 참조 type, 그리고 memory에 대한 이들의 관련성은 향후에 더 자세히 알아보기로 하고 지금은 최소한의 설명으로만 진행하고자 합니다.


type과 관련해서는 크게 2가지로 나눌 수 있는데 값 type과 참조 type이 그것입니다.

값 type은 상대적으로 단순하며 이들을 memory에 할당하기 위해 new keyword를 사용할 필요가 없지만 literal을 사용해 값을 설정할 수 없을 때 값자체를 초기화하기 위해 new를 사용하기도 합니다.

참조 type은 값 type에 비해 약간은 더 복잡한데 명시적으로 memory를 할당하기 위해 new keyword를 사용해야 하며 이때 new는 자신의 상태를 초기화하기도 합니다.

예를 들어 변수를 선언할 때 int나 DateTime과 같은 값 type에서는 해당 memory공간만 할당되며 Car와 같은 참조 type은 할당되지 않습니다.

//System.Int16 값을 저장하기 위해 stack memory상에서 2byte를 할당합니다.
short s;

//System.Int64 값을 저장하기 위해 stack memory상에서 8byte를 할당합니다.
long l;

//System.DateTime 값을 저장하기 위해 stack memory상에서 8byte를 할당합니다.
DateTime rd;

//stack memory에 할당되는 것은 heap에 있는 Car개체의 memory주소를 갖기위한 것입니다. 이 상태에서 c는 null값을 가집니다.
Car c;

 

● 변수 s는 0 값을 가지며 stack memory상에서 2byte가 할당됩니다.
● 변수 l은 0값을 가지며 stack memory상에서 8byte가 할당됩니다.
● 변수 rd는 0001-01-01 값을 가지며 stack memory상에서 8byte가 할당됩니다.
● 변수 c는 null값을 가지며 stack memory상에서 4byte가 할당됩니다. 그리고 아직 개체에 할당된 heap memory는 존재하지 않습니다.

아래 예제는 위 예제에서 값을 할당하는 사례를 나타내고 있습니다.

short s = 10;
l = 50_000_000;
rd = new(1980, 12, 1);
c = new();


특히 rd와 c의 경우 명시적으로 아래와 같이 값을 할당할 수 있으며 비교적 C#의 초기 version부터 사용해 온 방법입니다.

rd = DateTime(1980, 12, 1);
c = new Car(); 또는 c = new Car("sedan", "yellow");


● s, l, rd는 이미 stack memory에 할당되어 있으며 기본적으로 할당되는 값과 다른 값을 원하는 경우 new keyword를 사용해 값을 초기화합니다.
● c는 개체를 heap memory에 할당하기 위해 new를 사용해야 합니다. 이때 =연산자는 stack상에서 할당된 memory의 주소를 저장하게 됩니다. 이때 Car와 같은 참조 type은 종종 new를 통해 호출가능한 다수의 생성자를 가지고 있을 수 있는데, 기본 생성자의 경우 개체의 모든 상태에 대해 기본값을 할당하지만 인수를 가진 매개변수의 경우 인수를 사용해 개체의 상태에 다른 값을 할당할 수 있습니다.

생성자에 관해서는 추후에 자세히 알아볼 것입니다.

11) 개체의 초기화를 위해 형식화된 new 식 사용하기

C# 9에서 Micorsoft는 개체의 초기화를 위한 형식화된 new(target-typed new)라는 새로운 문법을 도입하였습니다. 이를 통해 개체를 초기화할 때는 우선 type을 명시한 뒤 다시 type을 명시하지 않고도 new만을 사용해 개체를 초기화할 수 있습니다.

XmlDocument xml = new();


값을 설정해야 할 field 또는 속성을 가진 type인 경우에도 아래 예제와 같이 추론될 수 있습니다.

Car c = new();
c.ReleaseDate = new(2025, 6, 27);

class Car
{
    public DateTime ReleaseDate;
}


개체를 초기화하는 이러한 방법은 다수의 동일한 개체를 가진 배열이나 collection에서 특히 유용하게 사용될 수 있습니다.

List<Car> cars = new()
{
    new() { ReleaseDate = new(2025, 6, 27) },
    new() { ReleaseDate = new(2025, 3, 14) },
    new() { ReleaseDate = new(2025, 9, 6) }
};

 

array와 collection에 관해서는 추후에 자세히 알아볼 것입니다.

가능하면 개체를 초기화하기 위해 형식화된 new 식을 사용하길 권장합니다. code의 양이 적을 뿐만 아니라 그러한 code를 읽을때도 변수의 type을 이해할 수 있고 var와 같은 지역변수에 제한되지 않습니다. 따라서 이전 C# 9 compiler를 사용해야 하는 경우가 아니라면 형식화된 new식은 code를 단순화하는데 도움이 될것입니다. Visual Studio에도 형식화된 new식을 사용하기 위한 refactoring기능이 있으며 해당 문서는 아래 link에서 확인하실 수 있습니다. https://learn.microsoft.com/en-us/visualstudio/ide/reference/use-new?view=vs-2022

 

12) type에 기본값 설정하기 및 가져오기

string을 제외한 대부분의 원시 type은 값 type인데 이것은 말 그대로 값을 가져야 한다는 것을 의미합니다. 이러한 type의 경우 default() 연산자를 사용하여 매개변수로 type을 던져주면 해당 type이 가지는 기본값을 확인할 수 있고 default keyword를 사용하면 type의 기본값을 할당할 수 있습니다.

 

string type은 참조 type입니다. 이는 string변수가 값자체가 아닌 값에 대한 memory주소를 포함한다는 것을 의미합니다. 참조 type 변수는 null 값을 가질 수 있는데 변수가 아직 어떠한 것도 참조하지 않고 있다는 것을 의미하는 literal에 해당하며 null은 모든 참조 type의 기본값에 해당합니다.


값과 참조 type에 대해서는 추후에 자세히 알아볼 것입니다.

 

아래 예제에서는 int, bool, DateTime 그리고 string에 대한 기본값을 알아보는 방식을 나타내고 있습니다.

Console.WriteLine($"int default = {default(int)}");
Console.WriteLine($"bool default = {default(bool)}");
Console.WriteLine($"DateTime default = {default(DateTime)}");
Console.WriteLine($"string default = {default(string) ?? "<NULL>"}");
?? 연산자는 대상변수가 null인 경우 ?? 연산자 뒤에 내용을 대신 반환하도록 합니다. 따라서 예제에서 default(string)은 null이므로 문자열로 <NULL>이 반환될 것입니다.

 

code를 실행하고 결과를 확인합니다. DateTime의 경우 code를 실행하는 computer의 현재 문화권에 따라 날짜와 시간형식이 다르게 표시될 수 있습니다.


이제 number 변수를 사용한 아래 구문을 추가합니다.

int age = 33;
Console.WriteLine($"age set to: {age}");
age = default;
Console.WriteLine($"age reset to its default: {age}");


예제에서는 age에 값을 설정한 이후 다시 default를 통해 기본값으로 할당하고 있습니다. 위 code를 실행하면 다음과 같은 결과를 표시할 것입니다.

5. Console App 살펴보기

 

우리는 이미 예제를 위해 기본 console app을 생성하고 사용해 왔습니다. 그러면서 지금까지는 그냥 지나왔지만 이에 대한 조금 더 의미 있는 내용을 다뤄보고자 합니다.


Console app은 명령줄을 통해 실행되는 text기반의 program이며 대부분 아주 간단한 동작을 수행하기 위해 사용됩니다.

 

또한 필요하다면 여기에 인수를 전달하여 동작을 제어할 수도 있습니다. 예를 들어 dotnet명령의 경우도 console app이라 할 수 있는데 아래와 같은 명령을 통해 현재 folder의 이름을 사용하여 F#언어의 새로운 console app을 생성할 수 있습니다.

dotnet new console -lang "F#" --name "MyConsole"

 

1) 사용자의 상호작용

Console app이 수행하는 가장 일반적인 작업은 사용자의 입력을 받고 사용자에게 어떤 결과를 출력하는 것입니다. 지금까지는 몇 차례 예제를 통해 어떤 결과를 출력하기 위해서 WriteLine method를 사용해 왔습니다. 그런데 만약 line의 끝에서 Carriage return을 하지 않고자 한다면, 예를 들어 해당 line의 끝에서부터 계속 특정한 문자열을 더 길에 계속 작성하고자 한다면 Write method를 사용할 수 있습니다.

Console.Write("A");
Console.Write("B");
Console.Write("C");

 

위 에제는 다음과 같이 3개의 문자를 동일한 line안에서 작성할 것입니다.

 

따라서 만약 Carriage return을 사용하면서 console에 3개의 문자를 쓰고자 한다면 WriteLine method를 호출해야 합니다.

Console.WriteLine("A");
Console.WriteLine("B");
Console.WriteLine("C");


위 code를 실행하면 3개의 line의 걸쳐 문자를 출력하고 cursor를 4번째 line에 남겨둘 것입니다.

 

2) Numbered positional arguments를 사용한 형식화

형식화된 문자열을 생성할 수 있는 방법 중 하나는 numbered positional arguments를 사용하는 것입니다.

이 기능은 기본적으로 Write와 WriteLine와 같은 method에서 지원하는데 이 기능을 지원하지 않는 method의 경우 문자열 매개변수는 string에 있는 Format method를 사용해 형식화할 수 있습니다.

간단한 예제를 작성해 보기 위해 기존 Study-02 folder에 StringFormat이름의 project를 추가합니다. 그리고 Program.cs에서 기존의 구문을 모두 삭제한 다음 몇 가지 숫자형 변수를 선언하고 이들을 console에 출력하는 구문을 아래와 같이 작성합니다.

int numberOfhats = 5;
decimal pricePerHat = 10_000;

Console.WriteLine(format: "{0} hat cost {1:C}", arg0: numberOfhats, arg1: pricePerHat * numberOfhats);

string formatted = string.Format(format: "{0} hat cost {1:C}", arg0: numberOfhats, arg1: pricePerHat * numberOfhats);
Console.WriteLine(formatted);


문화권에 따라 위 예제를 작성할 때 

using System.Globalization;
CultureInfo.CurrentCulture = CultureInfo.GetCultureInfo("ko-KR");


처럼 특정 국가로 문화권을 강제해야 할 수도 있습니다. 그렇지 않으면 예제를 실행하는 computer에 따라 다른 국가의 통화단위가 표시될 수 있습니다. 기본문화권 설정 및 다른 문화권설정등 문화권에 관한 자세한 사항은 추후에 자세히 알아볼 것입니다.

Wite, WriteLine와 Format method는 기본적으로 번호가 매겨진 3개의 인수(arg0, arg1, arg2)를 사용할 수 있습니다. 만약 더 많은 값을 전달해야 한다면 이러한 인수의 명칭은 더 이상 사용할 수 없습니다.


Program.cs에서 아래와 같이 3개와 5개의 인수를 console에 사용하는 문을 작성합니다.

Console.WriteLine("{0}, {1}, {2}.", arg0: "A", arg1: "B", arg2: "C");
Console.WriteLine("{0}, {1}, {2}, {3}, {4}.", "A", "B", "C", "D", "E");
일단 형식화된 문자열에 익숙해지면 arg0, arg1... 과 같이 매개변수에 이름을 부여하지 않아도 아무런 문제가 되지 않을 수 있습니다. 위 예제에서는 학습동안에 잠깐 0과 1이 어디에 표시되는지를 나타내기 위해 이름을 명명하는 방식을 소개한 것입니다.

 

3) Rider에서의 boxing에 대한 경고

만약 Rider를 사용 중이며 Unity 지원 plugin을 설치했다면 boxing에 대한 많은 경고를 보게 될지도 모릅니다. Boxing이 발생하는 흔한 경우중 하나는 int나 DateTime와 같은 값 type을 문자열 형식화를 위해 선택적 매개변수로 전달하는 경우입니다. 이는 일반적인 .NET runtime이 아닌 조금은 다른 memory garbage collector를 사용하기 때문에 Unity project에서 발생하는 문제입니다. 지금까지의 예제를 비롯해 비 Unity project에서는 큰 관련성이 없으므로 해당 boxing에 대한 경고를 무시해도 됩니다. 이러한 Unity만의 issue사항은 아래 link에서 좀 더 확인하실 수 있습니다.


https://docs.unity3d.com/Manual/performance-optimizing-code-managed-memory.html#boxing

 

Unity - Manual: Optimizing your code for managed memory

Reference type management Optimizing your code for managed memory C#’s automatic memory management reduces the risk of memory leaks and other programming errors, in comparison to other programming languages like C++, where you must manually track and fre

docs.unity3d.com

 

4) 보간문자열을 사용한 형식화

C# 6 이후부터는 보간문자열이라는 좀 더 간편한 기능을 사용할 수 있습니다. $문자로 접두사 된 문자열은 변수의 이름이나 표현식에 중괄호를 사용하여 변수나 표현식에 해당하는 현재값을 문자열의 특정위치에서 표시할 수 있습니다.

이와 관련된 예제를 만들어 보기 위해 Program.cs에서 다음과 같은 code를 작성합니다.

Console.WriteLine($"{numberOfhats} hat cost {pricePerHat * numberOfhats:C}");

 

코드를 실행하고 결과를 확인합니다.

 

code를 더 간소화하기 위한 보간문자열은 사람들이 읽기게 더 쉽기는 하지만 지역화된 resource file에서는 읽을 수 없다는 단점이 있습니다.

아래 예제는 설명을 위한 것으로 실제 project에서는 사용할 수 없습니다.

C# 10 이전에 string은 오로지 +연산자를 사용한 경우로만 문자열의 연속성을 나타낼 수 있었습니다.

private const string firstname = "susun";
private const string lastname = "kim";
private const string fullname = firstname + " " + lastname;

 

그러나 C# 10 이부터는 $접두사를 사용한 보간문자열을 통해 아래와 같이 구현할 수 있습니다.

private const string fullname = $"{firstname} {lastname}";


다만 이러한 방식은 문자열 상수값을 포함하는 경우에만 사용할 수 있으며 runtime에서 data type이 변환될 수 있는 숫자와 같은 다른 type에서는 작동하지 않습니다. 또한 Program.cs에서의 top-level program에서 private const선언은 입력할 수 없습니다. 추후에 이것에 관해 더 자세히 알아볼 것입니다.

Unity project에 대한 일부 code를 작성한다면 보간문자열은 boxing을 피하기 위한 손쉬운 방법이 될 수 있습니다.

 

5) 서식 문자열

변수나 표현식은 comma나 colon다음에 서식 문자열을 사용하여 서식화할 수 있습니다.

예를 들어 N0 서식 문자열은 천 단위로 분리된 숫자에 소수점이 없음을 의미하며 C 서식 문자열은 통화를 의미합니다.

이때 통화서식의 표현방식은 현재 문화권에 의해 결정되는데, 만약 UK문화권의 PC상에서 숫자나 통화 서식을 사용하는 code를 실행한다면 천단위 구분자로 ,를 통한 영국의 통화단위를 사용하게 되지만 독일(Germany) 문화권의 PC라면 점(.)을 사용한 euro단위를 사용하게 됩니다.

서식화에 대한 문법형식은 아래와 같습니다.

{ index [, aligment ] [: 형식화문자열 ]}


각각의 서식 item은 정렬방식을 가질 수 있는데 이러한 정렬방식은 Table의 정해진 공간 안에서 왼쪽 혹은 오른쪽 정렬이 필요한 경우 해당 방식에 맞게 값을 표현할 때 사용됩니다. 정렬방식은 정수로 표현되는데 양수인 경우 오른쪽 정렬을, 음수인경우 왼쪽 정렬을 의미합니다.

예를 들어 학생들의 점수를 표를 통해 나타내고자 할때 학생들의 이름은 10글자 공간을 기준으로 왼쪽 정렬을, 점수는 8자 기준으로 소수점 없이 오른쪽정렬로 표현하고자 한다면 아래와 같이 구현할 수 있습니다.

string student1 = "kim";
int point1 = 22200;

string student2 = "hong";
int point2 = 1190;

Console.WriteLine(format: "{0, -10} {1, 8}", arg0 : "Name", arg1 : "Point");
Console.WriteLine(format: "{0, -10} {1, 8:N0}", arg0 : student1, arg1 : point1);
Console.WriteLine(format: "{0, -10} {1, 8:N0}", arg0 : student2, arg1 : point2);


code를 실행하여 다음과 같이 적용된 정렬효과와 숫자서식화의 결과를 확인해 보시기 바랍니다.


6) 사용자정의 숫자 서식화

다음과 같은 사용자 숫자 서식화 code를 사용하면 숫자 서식화를 완벽하게 제어할 수 있습니다.

서식CODE 설명
0 0은 상용구로서 0에 해당하는 숫자가 표현되는 경우 해당숫자로 바꾸고 그렇지 않으면 0을 그대로 사용합니다. 예를 들어 0000.00 서식에서 값이 123.4가 사용되었다면 0123.40이 표현됩니다.
# 숫자 상용구로서 #(hash)에 해당하는 숫자가 표현되는 경우 해당숫자로 바꾸고 그렇지 않으면 공백으로 처리합니다. 예를 들어 ####.## 서식에서 값이 123.4가 사용되었다면 123.4가 표현됩니다.
. 소수점을 지정하는 것으로 숫자중에서 소수점의 위치를 설정합니다. 이때 해당 문화권에 따라 소수점이 달리 표현될 수 있는데 예를 들어 US에서는 점(.)으로, French에서는 comma(,)가 사용됩니다.
, 단위분리를 의미합니다. 각각의 단위사이에 지역화된 단위 분리자를 삽입하는 것으로 예를 들어 0,000서식에서 1234567값은 1,234,567로 표현됩니다. 또한 각 comma마다 1000의 배수로 나누어 숫자의 배율을 조정하는데도 사용됩니다. 예를 들어 0.00,,서식에서 숫자 1234567은 ,,이 1000을 두번 나눈다는 것을 의미하므로 1.23이 됩니다.
% Percetage 상용구로서 값에 100을 곱하고 백분율 문자를 추가합니다.
\ 확장문자열로서 다음 문자를 서식code로 취급하는 대신 literal문자로 취급합니다. 예를 들어 \##,###\# 서식에서 값 1234는 #1,234#이 됩니다.
; 영역분리자로서 음수, 양수, 0숫자에 대한 다른 서식 문자열을 정의합니다. 예를 들어 [0];(0);Zero 서식에서 13은 [13], -13은 (13) 그리고 0은 Zero가 됩니다.
이외 사용자정의 숫자 서식 code는 아래 link를 통해 확인하실 수 있습니다. https://learn.microsoft.com/en-us/dotnet/standard/base-types/custom-numeric-format-strings
 

Custom numeric format strings - .NET

Learn how to create a custom numeric format string to format numeric data in .NET. A custom numeric format string has one or more custom numeric specifiers.

learn.microsoft.com

 

C나 N 같은 표준 숫자 서식 code를 사용하여 숫자 서식을 간단하게 적용할 수 있습니다. 또한 숫자에 대한 정밀도를 지원하므로 원하는 정밀도만큼 숫자를 표현할 수 있습니다. 기본값은 2자리입니다. 다음표는 가장 일반적으로 사용되는 code를 소개하고 있습니다.

서식CODE 설명
C or c 통화입니다. 예를 들어 US문화권에서 C서식인 경우 123.4는 $123.40이며 C0에서 123.4는 $123이 됩니다.
N or n 숫자입니다. 선택적 음수 부호와 group화 문자를 포함된 정수입니다.
D or d 십진수로 선택적 음수 부호가 있지만 group화 문자는 포함되지 않은 정수입니다.
B or b 이진수입니다. 예를 들어 B서식에서 13값은 1101이 되며 B8서식에서 13은 00001101이 됩니다.
X or x 16진수입니다. 예를 들어 X서식에서 255는 FF가 되며 X4서식에서 255값은 00FF가 됩니다.
E or e 지수 표기법으로 E서식에서 1234.567은 1.234567000E+003이 되며 E3서식에서 1234.567값은 1.23E+003이 됩니다.
이외 표준 숫자 서식 code는 아래 link를 통해 확인하실 수 있습니다. https://learn.microsoft.com/en-us/dotnet/standard/base-types/standard-numeric-format-strings

 

사용자 정의 서식 code를 사용하면 날짜와 시간에 대한 서식 또한 완벽하게 제어할 수 있습니다.

서식CODE 설명
/ 날짜 부분 분리자입니다. 다양한 문화권에서 다양하게 표현될 수 있는데 예를 들어 en-US에서는 /로 사용되지만 ko-KR에서는 -(dash)를 사용합니다.
\ 확장문자열입니다. literal문자에서 특정한 서식 code를 사용하고자 할때 유용하게 사용될 수 있습니다. 예를 들어 h \h m \m 서식에서 AM 9:30은 9 h 20 m으로 표현됩니다.
: 시간 부분 분리자입니다. 다양한 문화권에서 다양하게 표현될 수 있는데 예를 들어 en-US에서는 :로 사용되지만 fr-FR에서는 .(dot)이 사용됩니다.
d, dd 1에서 31까지 또는 0이 붙은 01에서 31까지 날짜를 의미합니다.
ddd, dddd 월 또는 월요일처럼 요일을 나타내는 줄임말 또는 완전한 이름입니다. 이 이름은 문화권에 따라 다르게 표현될 수 있습니다.
f, ff, fff 10초, 100분의 1초, milli초 단위입니다.
g 기간 또는 시대를 의미하는 것으로 예를 들어 A.D. 와 같은 표시입니다.
h, hh 시간을 나타내는 것으로 1부터 12까지 또는 01부터 12까지 12시간을 사용합니다.
H, HH 시간을 나타내는 것으로 1부터 23까지 또는 01부터 23까지 24시간을 사용합니다.
K 표준 시간대 정보입니다. 미지정 표준시간대는 null이며 UTC의 경우 Z, UTC에서 조정된 지역 시간의 경우 -8:00과 같은 값입니다.
m, mm 분을 나타내는 것으로 0~59또는 00에서 59까지입니다.
M, MM 월을 나타내는 것으로 1~12또는 01에서 12까지입니다.
MMM, MMMM Jan 또는 January처럼 월을 나타내느 명칭의 약칭 또는 완전한 이름입니다. 문화권에 따라 다르게 표현될 수 있습니다.
s, ss 0~59 또는 00~59까지 초를 나타냅니다.
t, tt AM/PM에서 첫글짜 또는 두글자 모두를 의미합니다.
y, yy 현재 세기에 대한 연도표시로 0~99 또는 00에서 99까지입니다.
yyy 최소 3자리 부터 필요한 만큼 사용할 수 있습니다. 예를 들어 1 A.D.는 001입니다.
yyyy, yyyyy 4자리 혹은 5자리 수의 년도입니다.
z, zz UTC로 부터의 offset시간이며 zero을 사용하거나 사용하지 않습니다.
zzz UTC로 부터의 offset시간과 분이며 +04:30처럼 zero를 사용합니다.
박스 : 이외 표준 서식 code는 아래 link를 통해 확인하실 수 있습니다. https://learn.microsoft.com/en-us/dotnet/standard/base-types/custom-date-and-time-format-strings

 

d나 D와 같은 좀 더 간소화된 code를 사용하면 표준 날짜 및 시간대 서식을 적용할 수 있습니다.

서식CODE 설명
d 짧은 날짜 pattern으로 문화권에 따라 표현방식이 달라질 수 있습니다. 예를 들어 en-US라면 M/d/yyyy가 되며 ko-KR이라면 yyyy-MM-dd가 됩니다.
D 긴 날짜 pattern으로 문화권에 따라 표현방식이 달라질 수 있습니다. 예를 들어 en-US라면 mmmm, MMMM d, yyyy가 되며 fr-FR이라면 mmmm, dd MMMM yyyy가 됩니다.
f 완전한 날짜/시간 pattern이며 간소한 시간을 사용합니다. 문화권에 따라 다르게 표현될 수 있습니다.
F 완전한 날짜/시간 pattern이며 긴 시간을 사용합니다. 문화권에 따라 다르게 표현될 수 있습니다.
m, M 월/일 pattern이며 문화권에 따라 다르게 표현될 수 있습니다.
o, O 표준화된 pattern이며 2025-12-01T10:15:30.0000000-08:00처럼 왕복에 대한 날짜/시간값을 직렬화하는데 적절합니다.
r, R RFC1123 pattern입니다.
t 짧은 시간 pattern입니다. 문화권에 따라 다르게 표현될 수 있습니다. 예를 들어 en-US라면 h:mm tt가 되며 fr-FR이라면 HH:mm이 됩니다.
T 긴 시간 pattern으로 문과권에 따라 다르게 표현될 수 있습니다. 예를 들어 en-US라면 h:mm:ss tt가 되며 fr-FR이라면 HH:mm:ss가 됩니다.
u 범 정렬 가능한 날짜/시간 pattern이며 2025-12-01 13:49:21Z와 같이 표현됩니다.
U 범용 완전 날짜/시간 pattern입니다. 문화권에 따라 다르게 표현될 수 있으며 en-US라면 Monday, June 15, 2025 12:30:25 AM처럼 표현됩니다.
이외 표준 서식 code는 아래 link를 통해 확인하실 수 있습니다. https://learn.microsoft.com/en-us/dotnet/standard/base-types/standard-date-and-time-format-strings
'm' (USA에서 July 4 또는 UK에서 4 July와 같이 문화권별로 표현되며 연도를 생략하는 월/일에 대한 표준)과 'd m'(공백을 사이에 두고 날수와 월수를 4 7과 같이 표현하는 사용자정의 서식 문자열)이 다름에 주의가 필요합니다.

 

서식을 사용한 하나의 예제를 만들어보기 위해 아래와 같은 구문을 작성합니다. 구문은 decimal값을 두 번에 걸쳐 표현하는데 첫 번째는 현재 문화권을 사용한 통화형식이고 두 번째는 percentage입니다.

decimal d = 1234.56M;
Console.WriteLine("Currency : {0:C}, Percetage : {0:0.0%}", d);


위 예제를 실행하면 다음과 같은 결과를 표시할 것입니다.

 

7) 사용자로부터 입력받기


ReadLine method를 사용하면 사용자로부터 입력값을 문자열로 받을 수 있으며 실제 method를 실행하게 되면 사용자의 실제 입력이 있을때까지 대기합니다. 그러고 나서 Enter key를 누르게 되면 사용자가 입력한 내용이 무엇이든 그것을 문자열값으로 반환하게 됩니다.

실제 예제를 작성해 보기 위해 아래와 같이 사용자로 부터 이름과 나이를 요청하고 입력이 완료되면 입력된 내용을 다시 출력하는 code를 작성합니다.

 Console.Write("your name : ");
 string userName = Console.ReadLine();

 Console.Write("your age : ");
 string age = Console.ReadLine();
 
 Console.WriteLine($"{userName}, {age}");
기본적으로 .NET6 부터 null 가능 확인이 활성화되어 있습니다. 따라서 C# compiler는 ReadLine method가 string값 대신 null을 반환할 수 있으므로 2개의 경고 message를 표시하게 됩니다. 하지만 위와 같은 상황에서 ReadLine method가 null을 반환한다고 하더라도 문제 될 것은 없습니다.. 따라서 이러한 특정한 상황에서 나타나는 경고성 message는 불필요하므로 다음 방법을 통해 message가 사라지도록 할 것입니다.

 

userName변수에서 string에?를 추가해 줍니다.

string? userName = Console.ReadLine();

 

이러한 방법은 compiler에게 이미 null이 나올 수 있음을 예상하고 있으므로 따로 나에게 경고해 줄 필요는 없다는 것을 말해주는 것입니다. 변수가 null이라면 WriteLine method를 통해 값을 표시할 때 그냥 공백으로만 표현될 것입니다. 따라서 실제 null값이 생긴다 하더라도 예제가 동작하는 데는 아무런 문제가 없습니다. 하지만 만약 위 code이후에 userName변수의 특정 member에 접근해야 한다면 이때는 null인 경우에 대한 조치를 취해야 합니다.

 

이번에는 age변수 부분에서 Console.ReadLine() method뒤에(문이 끝나는 semicolon전에) !를 다음과 같이 붙여줍니다.

string age = Console.ReadLine()!;

 

이러한 방법을 null허용 연산자(null-forgiving operator)라고 하는데 이 경우 compiler에게 명시적으로 ReadLine method는 null 반환하지 않는다고 알려주는 것입니다. 따라서 compiler는 경고 message를 더 이상 표시하지 않게 됩니다. 따라서 실제 null이 발생하는 경우에 대해서는 전적으로 개발자의 책임하에 처리되어야 하는데 다행스럽게도 위 예제의 경우 Console type의 ReadLine method는 사용자가 아무런 값을 입력하지 않으면 공백 문자열을 반환하므로 null에 대한 처리가 따로 필요하지 않습니다.

 

예제를 실행하고 이름과 나이를 입력하고 나면 다음과 같은 결과를 표시할 것입니다.

 

예제를 통해 우리는 null이 발생할 수 있는 가능성이 있는 경우에 compiler가 표시하는 경고 message를 어떻게 제어하는지 알아보았습니다. 이와 관련해 null처리에 대한 좀 더 상세한 사항은 추후에 알아볼 것입니다.

 

8) ReadLine이 null을 반환하는 경우

 

ReadLine method는 표준 입력 stream으로부터 다음 줄을 읽어 들입니다. 이때 사용자가 어떠한 입력도 없이 Enter key를 누르게 되면 ReadLine은 null대신 공백 문자열을 반환합니다.

 

일반적인 Console app의 경우 EOF(end of stream) 도달하는 경우에만 null을 반환하는데 이는 표준 console환경에서 사용자의 입력에 의해 발생할 수 있는 경우는 아닙니다. 왜냐하면 EOF는 일반적으로 console이 닫히는 경우 전달되는 신호이기 때문입니다.

 

아래 code에서

string? userName = ReadLine();

 

userName이 null이 될 때는 표준 입력 stream이 redirect 되거나 EOF에 도달할 때 또는 일부 개발 혹은 자동화된 설정환경과 같이 EOF를 simulate 할 수 있는 환경에서 test를 수행하는 경우입니다.

 

그러나 일반적인 사용자 입력조건에서 null은 ReadLine method에서 절대 반환되지 않습니다.

 

9) Console 사용성 간소화하기

 

C#6부터 using문은 namespace를 import 하는 것뿐만 아니라 정적 class에 대한 import를 수행함으로써 code를 더욱 간소화할 수 있게 되었습니다. 이를 통해 Console.WriteLine와 같은 구문에서도 더 이상 Console type을 일일이 입력할 필요가 없어졌습니다.

(1) 단일 file에서의 정적 type import

대부분의 code editor에서는 'Find and Replace'와 같은 기능을 제공하고 있으므로 이를 사용하여 이전에 작성한 모든 Console을 간단하게 제거할 수 있습니다.

Program.cs의 첫 번째 line에 System.Console class를 정적 import 하도록 아래와 같은 문을 추가합니다.

using static System.Console;


그다음 System. 이후에 Console단어를 선택하고 Visual Studio를 사용하는 경우 Edit -> Find and Replace -> Quick Replace를, VS Code인 경우 Edit -> Replace를 선택합니다. 그러면 Console대신에 바뀔 단어를 입력할 수 있도록 dialog화면이 위에 표시될것입니다. 여기서 해당 화면의 상단 입력 box를 보면 선택한 'Console'단어가 있을텐데 이 단어를 'Console.'으로 변경합니다.


그 다음 Replace 입력칸은 비워둔 상태에서 Replace All button(Replace 입력 box오른쪽에 있는 2개의 button 중 오른쪽)을 click 하고 dialog화면을 닫습니다.

code의 내용은 바뀌었지만 현재 예제를 그대로 실행하면 이전과 동일하게 동작할 것입니다.

(2) project의 모든 code file에서 전역적으로 정적 type import 하기

하나의 code file에서만 Console class를 정적 import 하는 대신 경우에 따라 project의 모든 code file에 대한 전역 import가 필요한 경우도 있습니다.

전역적 type import를 해보기 위해 예제에서 System.Console를 import하는 문을 제거하고 project의 csproj file을 open 합니다. csproj file내용 중 <PropertyGroup> 영역 다음에 새로운 <ItemGroup> 영역을 아래와 같이 추가합니다. 이 기능은 .NET SDK의 암시적 using을 사용하는 것으로 System.Console에 대한 전역적 정적 import를 수행하기 위한 것입니다.

<ItemGroup Label="System.Console Import">
    <using Include="System.Console" Static="true" />
</ItemGroup>
예제에서 사용된 Label attribute는 필요에 따라 선택적으로 사용하는 것으로 주로 조직단위 개발에서 해당 요소가 무엇을 위해 사용되었는가를 명확히 알라기 위해 사용됩니다. 특히 다수의 <ItemGroup> 사용되는 경우 Label은 각 group이 무엇을 포함하고 있는지를 명확하게 알려주게 되는데 이를 통해 우리는 선택적으로 build 또는 다른 조건에 기반하여 특정 item을 포함하거나 제외할 수 있을 것입니다. 예제의 경우에는 그저 해당 group을 문서화하기 위한 용도로만 사용합니다.

 

예제를 실행하고 이전과 동일한 동작이 수행되는지 확인합니다.

향후에 예제를 위해 Console app을 생성할 때는 상기 요소를 추가하면 모든 C# file에서 작성되어야 하는 code를 간소화할 수 있습니다.

 

10) 사용자로부터 key입력받기

ReadKey method를 사용하면 사용자가 입력한 key입력을 확인할 수 있습니다. 해당 method는 사용자가 key 혹은 key 조합을 입력할 때까지 대기하다가 key를 누르게 되면 해당 정보를 ConsoleKeyInfo개체로 반환하게 됩니다.

아래 구문은 사용자에게 key 입력을 요구하고 입력된 정보를 표시하는 예제입니다.

Write("Press Key...");
ConsoleKeyInfo cki = ReadKey();

WriteLine();

WriteLine("Key : {0}, Char : {1}, Modifiers : {2}", cki.Key, cki.KeyChar, cki.Modifiers);

 

예제를 실행하고 k key를 눌러보면 아래와 같은 결과를 표시할 것입니다.

 

이번에는 Shift key를 누른 채로 k key를 눌러봅니다.

 

다시 예제를 실행하고 이번에는 F12 key를 눌러봅니다.

 

예제를 VS Code에 내장되어 있는 terminal에서 실행할 때 일부 key의 조합입력은 실제 해당 key의 입력이 처리되기도 전에 VS Code에서 가로채는 경우가 생길 수 있습니다. 예를 들어 Ctrl + Shift + X key는 sidebar의 Extension view를 보기 위한 단축 key입니다. 예제를 보다 완벽하게 test 하려면 command prompt 혹은 terminal을 project folder에서 열고 예제를 실행하시기 바랍니다.

 

11) console app으로 인수 전달하기

예제의 console app과 같이 어떠한 program을 실행할 때는 종종 특정한 인수값을 전달하여 program의 동작을 제어해야 하는 경우가 있습니다. 예를 들어 dotnet command-line 도구를 사용할 때 생성할 project template의 이름을 다음과 같이 전달할 수 있습니다.

dotnet new console
dotnet new mvc

 

그렇다면 인수로 전달한 값을 application에서는 어떻게 확인할 수 있을까? .NET 6 이전의 모든 .NET version에서 console app project template은 Main method에 args와 같은 매개변수를 사용하여 인수를 전달받을 수 있었습니다.

using System;

namespace Example
{
    class Program
    {
        static void Main(string[] args)
        {
            Console.WriteLine("Hello World!");
        }
    }
}

 

즉, string [] type의 args 인수를 Program class의 Main method에서 선언하면 인수의 값은 여기로 전달되는데 이때 인수를 console app으로 전달하기 위해 array가 사용된다는 것을 알 수 있습니다. 하지만 .NET6부터 console app project template에서 사용되는 top-level program에서는 Program class와 Main method가 args array의 선언과 함께 숨겨지게 됩니다. 하지만 분명한 사실은 이들은 숨겨진 것일 뿐 여전히 존재한다는 것입니다.

 

Command-line 인수는 공백에 의해 분리됩니다. hypen이나 colon과 같은 다른 문자는 인수의 값 일부로서 취급됩니다. 또한 인수값에 공백을 포함하기 위해서는 인수값을 홀따옴표나 큰따옴표로 감싸줘야 합니다.

 

만약 command line을 통해서 terminal window의 크기와 전경 및 배경색을 지정할 수 있고 이를 위해 색상의 이름과 크기값을 전달한다면 console app의 주요 진입점인 Main method의 args array를 통해 색상값과 크기에 대한 이 모든 값을 읽을 수 있습니다.

 

이에 대한 예제를 작성해 보기 위해 Study-02 folder에 Arguments라는 folder를 만들고 여기에 같은 이름의 Console App Project를 생성합니다.

 

그런 다음 Program.cs에서 기존 문을 모두 삭제하고 program으로 전달된 인수의 수를 표시하는 문을 아래와 같이 추가합니다.

Console.WriteLine($"argument count : {args.Length}");

 

예제를 실행하면 다음과 같은 결과를 표시할 것입니다.

 

이제 이렇게 만든 예제로 원하는 인수를 전달해 보겠습니다. Visual Studio를 사용한다면 Project의 속성에서 Debug tab으로 들어가 Open debug launch profiles UI를 click 합니다. 그리고 Command line arguments에 'first second third:3 "fourth 4"'라는 인수값을 입력합니다.

 

위와 같이 입력하고 나면 Project의 Properties folder안에 launchSettings.json 파일에도 동일한 내용이 적용되어 있음을 확인할 수 있습니다.

{
  "profiles": {
    "Arguments": {
      "commandName": "Project",
      "commandLineArgs": "first second third:3 \"fourth 4\""
    }
  }
}
launchSettings.json file은 VS Code의 .vscode/launch.json file에 해당합니다.

 

Visual Studio를 사용하지 않는다면 그냥 Terminal에서 dotnet run 명령 다음에 'first second third:3 "fourth 4"'인수값을 다음과 같이 그대로 입력하면 됩니다.

dotnet run first second third:3 "fourth 4"

 

위와 같이 실행하면 다음과 같은 결과를 표시할 것입니다.

 

Program.cs에서 인수의 길이를 표현하는 문 다음에 loop를 통해 인수로 전달된 값을 열거하는 아래의 문을 추가합니다.

foreach (string arg in args)
    Console.WriteLine(arg);

 

예제를 다시 실행하면 이번에는 전달받은 인수가 무엇인지를 아래와 같이 표시할 것입니다.

 

12) 인수에 대한 option 설정하기

Application에 전달하는 인수는 필요에 따라 그 값을 제한할 수 있습니다. 예를 들어 windows terminal에서 전경색과 배경색, Cursor size를 설정하기 위해 사용자가 해당 인수값을 전달한다고 했을 때, cursor의 size는 cursor cell아래부터의 시작을 의미하는 1부터 cursor cell의 높이에 대한 percentage인 100까지의 값이 되어야 합니다.

 

정적 class인 System.Console은 ForegroundColor BackgroundColor 그리고 CursorSize속성을 갖고 있는데 실제 적용은 해당 속성의 이름을 그대로 사용해 값을 설정함으로써 수행됩니다.

 

아래 예제는 사용자가 위와 같은 3개의 인수를 입력하지 않았을 경우 사용자에게 오류를 표시하며 해당 인수값을 읽어 windows terminal의 cursor크기 및 색상을 설정하도록 합니다.

if (args.Length < 3)
{
    Console.WriteLine("You must specify two colors and cursor size.");
    Console.WriteLine("e.g. dotnet run red yellow 50");
    return;
}

Console.ForegroundColor = (ConsoleColor)Enum.Parse(enumType: typeof(ConsoleColor), value: args[0], ignoreCase: true);
Console.BackgroundColor = (ConsoleColor)Enum.Parse(enumType: typeof(ConsoleColor), value: args[1], ignoreCase: true);
Console.CursorSize = int.Parse(args[2]);
CursorSize를 지정하는 부분에서 compiler는 해당 설정이 Windows에서만 지원된다는 경고를 표시할 수 있습니다. 또한 (ConsoleColor)및 Enum.Parse나 typeof와 같은 부분은 향후에 자세히 알아볼 기회가 있을 것이므로 그 의미에 대해서 크게 신경 쓰지 않아도 됩니다. 예제는 인수에 대한 설명을 위한 예제일 뿐입니다.

 

Visual Studio를 사용하는 경우 위에서 언급만 설명대로 인수값을 red yellow 50으로 변경합니다. 그 외의 경우라면 아래 명령을 통해 인수를 전달합니다.

dotnet run red yellow 50

 

위 명령을 실행하고 나면 Cursor의 크기가 50%가 되며 window의 색상이 아래와 같이 변경되었음을 확인할 수 있습니다.

 

macOS 또는 Linux라면 아마도 예외가 발생할 수 있습니다. 예외가 따로 발생하지 않더라도 일부 API의 경우 특정 platform에서는 호출이 실패할 수 있습니다. Windows상에서 동작하는 Console app이 Cursor size를 변경할 수 있지만 macOS는 반영되지 않습니다.

 

13) API를 지원하지 않는 platform 처리하기

 

위와 같이 지원되지 않는 platform에서 Application이 실행될 수 있다면 기본적으로 이러한 부분을 예외처리를 함으로써 해결할 수 있습니다. 예외처리는 try~catch문을 사용하는데 이에 관한 자세한 사항은 추후에 알아보겠지만 지금은 code를 따라 하는 것으로 충분합니다.


예제에서 cursor의 size를 변경하는 부분을 try문을 사용해 아래와 같이 처리합니다.

try
{
    Console.CursorSize = int.Parse(args[2]);
}
catch (PlatformNotSupportedException ex)
{
    Console.WriteLine($"{ex.Message}");
}

 

위와 같이 변경 후 macOS상에서 code를 실행하면 이전보다는 좀 더 친숙한 형태로 오류를 표시할 것입니다.


이와 관련된 또 다른 방법으로는 System namespace에 있는 OperatingSystem class를 사용하는 것입니다.

if (OperatingSystem.IsWindowsVersionAtLeast(major: 10))
{
    // Windows 10 부터 실행되는 경우
}
else if (OperatingSystem.IsWindows())
{
    // Windows 10 이전 version에서 실행되는 경우
}
else if (OperatingSystem.IsIOSVersionAtLeast(major: 14, minor: 5))
{
    // iOS 14.5 부터 실행되는 경우
}
else if (OperatingSystem.IsBrowser())
{
    // Blazor Application이 browser에서 실행되는 경우
}

 

OperatingSystem class는 Android나 iOS, Linux, macOS 및 Browser와 같은 다른 platform에서도 동일한 method를 가지고 있습니다. 또한 Browser의 경우에는 Blazor web component를 실행할 때 유용하게 사용될 수 있습니다.


다른 platform에 대한 예외처리를 하는 세 번째 방법은 조건부 compile 문을 사용하는 것입니다.


여기에는 #if, #elif, #else, #endif와 같이 조건부 compile을 수행하기 위한 4개의 전처리 지시자가 있는데 우선 #define을 통해 필요한 symbol을 아래와 같이 정의하고

#define NET7_0

 

해당 symbol에 전처리 지시문을 적용하는 방법으로 사용됩니다.

 

symbol은 아래와 같은 것으로 정의할 수 있습니다.

Framework symbol
.NET Standard NETSTANDARD2_0, NETSTANDARD2_1 등
.NET NET7_0, NET7_0_ANDROID, NET7_0_IOS, NET7_0_WINDOWS 등

 

아래 예제는 위의 symbol을 통해 지시자를 적용한 것으로 특정 platform에서만 compile 되는 문을 만들 수 있습니다.

#if NET7_0_ANDROID
// 여기에 android에서 실행될때 compile되는 문을 작성
#elif NET7_0_ISO
// 여기에 iOS에서 실행될때 compile되는 문을 작성
#else
// 그외 모든 경우에서 compile되는 문을 작성

 

728x90
저작자표시 비영리 변경금지

'.NET > C#' 카테고리의 다른 글

[C# 13과 .NET 9] 1. .NET 개요  (0) 2025.03.04
[C# 12와 .NET 8] 11. LINQ  (0) 2024.03.11
[C# 12와 .NET 8] 10. Entity Framework Core  (1) 2024.03.07
[C# 12와 .NET 8] 9. File, Streams, Serialization  (0) 2024.02.28
[C# 12와 .NET 8] 8. 공용 .NET Type  (0) 2024.02.23
    '.NET/C#' 카테고리의 다른 글
    • [C# 13과 .NET 9] 1. .NET 개요
    • [C# 12와 .NET 8] 11. LINQ
    • [C# 12와 .NET 8] 10. Entity Framework Core
    • [C# 12와 .NET 8] 9. File, Streams, Serialization
    클리엘
    클리엘
    누구냐 넌?

    티스토리툴바