23.01.11. - (수) 닷넷 정리 (진수형 잘 먹을게!)

김도익·2023년 1월 11일
0

C#

목록 보기
15/29

1. C#의 발전 이력 참고 자료

  • 2000년 7월에 개최된 Professional Developers Conference(PDC)에서 .NET 프로젝트와 함께 발표된 객체 지향 프로그래밍 언어(Object Oriented Programming Language)

  • MicroSoft에서 개발되었으며 1983에 등장한 C++의 특징과 1995년 등장한 Java에 특징을 적절하게 섞어놓은 언어이다

  • C#의 파일 확장자는 .cs 이다

  • C#은 하나의 코드만 작성하여도 다수의 플랫폼에 대응할 수 있는데 이를 가능하게 만드는 것이 .NET 이다

2. .NET 아키텍처란? 참고 자료

  • 하나의 언어로 다수의 플랫폼을 대응할 수 있는 이유는 중간에 계층이 존재하기 때문입니다.

  • C++ 으로 프로그램을 제작할 경우 각각의 운영체제에 맞추어 여러 프로그램을 만들어야 한다

    • 플랫폼 위에서 바로 구동하는 언어를 네이티브 언어(Native Language)라고 한다
  • 하지만 C#의 경우 운영체제와 C# 프로그램 사이에 계층이 존재하는데, 이를 .NET이라 한다. 이 .NET이 존재하기 때문에 하나의 코드로 여러 운영체제에서 동작할 수 있는 것이다

  • 이러한 .NET 프로그램같은 것들을 미들웨어(Middleware)라 한다.

  • 마이크로소프트는 어떤 플랫폼이던지 언어를 동작시킬 수 있도록 공용 언어 인프라(CLI; Common Language Infrastructure)라는 사양을 발표했는데, .NET은 이 사양에 맞춰 마이크로소프트가 구현한 프로그램인 공용 언어 런타임(CLR; Common Language Runtime)과 클래스 라이브러리 세트를 말하는 것이다

    // .NET 아키텍처의 계층도

  • 이러한 Middleware를 거치는 언어들은 한 단계를 추가적으로 거치기 때문에 필연적으로 느릴 수 밖에 없다

3. 빌드 과정의 이해

Tip
어셈블리란 C# 코드로 작성된 프로젝트가 빌드된 결과물을 뜻합니다.
결과물이란 실행 파일(exe)입니다.
dll은 어셈블리 파일을 뜻합니다.
어셈블리 파일이 만들어지고, 실행을 시키면 CLR이 어셈블리를 읽고, 네이티브 코드로 변환해준다. 그리고 변환 된 코드를 이용해서 프로그램을 실행시킨다.
이게 대략적인 빌드과정입니다.

  • C# 프로젝트를 빌드할 때 C# 코드는 CLI 사양을 준수하는 중간 언어(Intermediate Language)로 컴파일 된다.

    • 이 중간 언어는 CPU가 바로 해석 할 수 있는 것이 아니라 가상 머신(Virtual Machine)인 CLR이 CPU가 이해할 수 있도록 해석해준다.
  • IL코드와 프로그램에 사용되는 리소스가 함께 패키징 되어 어셈블리(Assembly)가 된다

  • Assembly는 서로 함께 사용되어 논리적 기능 단위를 형성하도록 빌드되는 타입 및 리소스의 컬렉션을 의미한다.

  • C/C++를 컴파일하면 생성되는 어셈블리 언어와는 다른 개념이기 때문에 혼동해서는 안된다
    C# 프로그램을 실행하면 어셈블리가 CLR에 로드 되는데, CLR은 IL 코드를 플랫폼에 따라 JIT컴파일 혹은 AOT컴파일 과정을 수행하여 네이티브 코드(Native Code)로 변환한다

    • JIT(Just-In-TIme)
      • 실행시키면서 변환을 시키는 과정으로 인터프리터(Interpreter) 방식이라고도 한다
      • 코드를 한 줄 한 줄 읽어내려가면서 실시간으로 기계어로 번역하는 과정이 진행된다
      • 동적 컴파일 기술이라고도 한다
      • 실행시간이 구조상 AOT보다 느리다
    • AOT(Ahead-Of-Time)
      • 이 방식은 코드를 전부 읽고 기계어로 변환한다
      • 본래 영어단어의 뜻을 생각하여 Ahead-Of, 실행시키기 전에라는 의미이다
      • 정적 컴파일 기술이라고도 한다
      • 실행시간이 구조상 JIT보다 빠르다
  • 다음은 .NET Framework에서의 소스코드가 변환되는 과정이다

4. 공용 타입 시스템(CTS; Common Type System)이란?

  • .NET에서는 여러 .NET 언어를 지원하기 위해 공용 타입 시스템(CTS; Common Type System)을 지원한다

  • .NET의 모든 형식은 값 타입(Value Type) 혹은 참조 타입(Reference Type)으로 구분되며 모든 타입은 기본 타입인 System.Object 에서 파생된다

  • C# 에서는 아래와 같은 계층을 가지고 있다

5. 값 타입(Value Types)

  • C#에서 값 타입은 Struct / Enumeration / 그 외 기본 제공 타입으로 구성된다

  • 값 타입은 다음과 같은 특징을 갖는다

    • 구조체를 제외한 모든 타입은 System.ValueType에서 파생된다
    • 스택 메모리에 직접 값이 포함된다. 복사가 일어난다는 의미
    • 상속이 불가능하다
    • 구조체 멤버 중 참조 타입이 있다면 메모리 주소가 복사된다. 이를 얕은 복사(Shallow Copy)라고 하며, 반대는 깊은 복사(Deep Copy)라고 한다
    • 얕은 복사와 깊은 복사에 대한 설명은 링크로 대체한다
      // https://www.geeksforgeeks.org/shallow-copy-and-deep-copy-in-c-sharp/

5-1. 얕은 복사 깊은복사

  • 얕은 복사

    • 가리키는 주소값만 복사한다.
    • 새로운 배열을 생성하면 가리키는 주소가 달라진다.
  • 깊은 복사

    • 비유를 하자면 메모리를 ctrl c, crtl v 한 것이다.
    • 새로운 배열을 생성하고 값을 초기화 시켜주면, 기존의 갖고 있던 동일한 데이터를 내 마음대로 바꾸고 사용할 수 있다.

      `

6. 참조 타입(Reference Types)

  • C#에서 참조 타입은 class / delegate / array / interface가 있다
  • 참조 타입은 다음과 같은 특징을 갖는다
    • 힙 메모리에 인스턴스가 할당된다
    • 참조 타입의 변수는 인스턴스의 주소에 대한 참조를 가진다
    • 널(Null)을 할당할 수 있다

7. 박싱과 언박싱

  • 박싱(Boxing)은 값 타입을 참조 타입으로 변환하는 프로세스입니다.

    • 박싱이 일어날 땐 object 타입의 새로운 인스턴스를 생성해 값을 힙으로 복사합니다.
    • 박싱은 암시적으로 이뤄집니다.
  • 언박싱(Unboxing)은 박싱된 인스턴스를 다시 값 타입으로 변환하는 프로세스입니다.

    • 언박싱이 이뤄질 땐 힙에서 스택으로 값이 복사됩니다.
    • 언박싱은 명시적으로 이뤄집니다.
  • 박싱과 언박싱은 많은 계산 과정을 필요로 하기 때문에 코드를 작성할 시 주의가 요구됩니다

  • 박싱(Boxing)은 값 타입을 Object 타입 또는 값 타입에서 구현된 임의의 인터페이스 타입으로 변환하는 프로세스이다

    • 이 프로세스는 CLR에서 Value 형식을 System.Object 인스턴스로 랩핑 하고 관리되는 힙에 저장하는데 사용된다

    • 값 유형을 박싱하면 값이 복사되는 힙에서 값 유형과 동일한 유형의 값을 참조하는 객체 참조가 생성된다

  • 언박싱(Unboxing)은 박싱된 인스턴스에서 값 타입을 추출하는 프로세스다

    • 이 프로세스에서는 개체가 주어진 값 유형의 박스형 값인지 확인한다
    • 인스턴스의 값을 값 유형 변수로 복사한다
  • 박싱이 일어날 땐 object 타입의 새로운 인스턴스를 생성해 값을 힙으로 복사하고, 언박싱이 이뤄질 땐 힙에서 스택으로 값이 복사된다

  • 박싱은 암시적으로 이뤄지며, 언박싱은 명시적으로 이뤄진다. 이 과정을 좀 더 도식화하면 다음과 같다
  • 박싱과 언박싱이 일어날 때는 병목이 일어난다

  • 박싱과 언박싱을 수행하는데에는 많은 계산 과정이 필요하기 때문에 많은 수의 박싱을 수행할 경우 성능저하의 우려가 있으므로 최대한 사용을 지양해야 한다

자 근데 이게 어떻게 가능할까요? 박싱과 언박싱이 왜 되는걸까요?

  • 모두 부모 객체인 object타입 덕분입니다.

  • 그렇기 때문에 모든 타입이 object로 변환이 가능한 것입니다.

  • object타입은 가장 상위타입 입니다.

  • 정리하자면 object타입이 참조 형식(reference types)이기 때문에 object형으로 형변환이 일어 나는 것을 박싱이라고 합니다.

  • object에서 다시 원상태로 복귀 시키는 것을 언박싱이라고 하는 것입니다.

실제 예시를 보겠습니다.

  • 사용 방식은 기존에 형변환 하는 방법과 같습니다.

  • 차이가 있다면 대상이 object가 들어 갔다는 것입니다.

  • 박싱 예제

    • 박싱 먼저 보겠습니다. 박싱은 암시적으로 이뤄진다고했죠?


      // 명시적으로도 가능합니다!
  • 언박싱 오류 예제

    • 언박싱도 확인해보겠습니다. 언박싱은 기존에 박싱한 것을 다시 푸는 작업이라고 했습니다.

      - 아래와 같이 object타입을 하위차원으로 형변환을 하면 오류가 발생합니다.

  • 언박싱 예제

    • 다시 말해 아래와 같이 박싱한걸 다시 푸는 형태가 정상적인 방법입니다.

    • 이때는 상위차원에서 하위차원으로 푸는 것이기 때문에 명시적으로 적어줘야 한다는 것을 잊어선 안됩니다.

박싱과 언박싱을 왜 사용할까요?

  • 가령 아래와 같은 배열을 만들고 싶다고 가정하겠습니다.

  • 우리는 배열을 만들때 특정 타입을 지정해줘야합니다. 여기선 int로 지정했습니다.

  • 그렇게 때문에 string 타입은 넣을 수가 없습니다.

  • 하지만 이때 가장 상위 타입인 object를 배열 타입으로 지정해준다면 다양한 타입을 다 넣을 수 있다는 장점이 있습니다.

  • 이때 내부적으로 데이터가 배열에 들어갈때 박싱(boxing)이 일어나게 됩니다.

  • 그리고 출력시 다시 언박싱(unboxing)을 하게 됩니다.

  • 정말 상당히 편리해 보입니다. 하지만 편리하다고 좋은 것이 아닙니다. 왜냐면 메모리상 비효율성이 발생되기 때문입니다.

  • 값 형식이 참조 형식으로 바뀔때, 스택에 있는 값을 복사하여 힙에 할당시키게 되고, 언박싱시 다시 스택에 가져오는 작업을 하면서 힙에 가비지가 쌓이게 됩니다. 뿐만 아니라 단순히 힙에 넣는 작업에 비해 박싱을 통할시 20배 정도 더 많은 시간이 소요되고, 언박싱시 4배 정도의 시간이 소요됩니다.

  • 그렇다면 object의 장점도 가져오면서 성능적 이슈를 해결할 방법은 없을까요?
    있습니다. C2.0을 넘어오면서 생긴 제너릭(generic)이 이 문제들을 해결해줍니다.

닷넷 이해를 높이기 위한 추가 설명입니다.

상당히 라이브하게 적었고, 지극히 제 주관적인 입장에서 배운대로 적은 것입니다.

  • .net 이란? .net 아키텍처란?
    ms에서 발표한 CLI 사양에 맞춰 MS가 구현한 CLR과 Class Library세트의 오픈소스 개발자 플랫폼

  • C, C++ 등 미들웨어가 존재하지 않는 언어의 경우 운영체제에 맞추어 다른 코드를 작성해야 한다.

  • C# 의 경우 미들웨어가 존재하기 때문에 코드 하나를 작성해도 각각의 운영체제에 맞추어 자동적으로 변환되어 프로그램이 완성된다
    그래서 핸드폰 안드로이드, 컴퓨터 윈도우 서로 다른 운영체제를 가지고 있지만 같은 게임을 할 수 있다.

  • Console.WriteLine(“Hello world”);

    windows, linux, macOS에서 모두 작동이 가능한 이유는?
    답은 CLI에 있다

예를 들어
c#에서 칠판을 작성하면 아래 언어로 CLI가 해석을 해주고, 코드를 읽게 해준다.
windows // whiteboard - 미국인 일시
linux // 黒板 - 일본인 일시
macOS // bảng đen - 베트남인 일시

windows // 미국 - whiteboard 해석한다
linux // 일본 - 黒板 해석한다
macOS // 베트남 - bảng đen 해석한다

CLI가 모든 운영체제에 맞게 번역해주니, 코드 하나를 작성해도 각각 운영체제에 사용할 수 있는 것이다.


빌드

Console.WriteLine();을 적는다면 c# 컴파일러가 IL 코드로 변환하고 이때 프로그램으로서 실행시키기 위한 다른 리소스 등 합쳐 exe 또는 dll 이라는 어셈블리 파일로 만든다.

실행

CLR이 만들어진 어셈블리 파일을 로드하여 각각의 운영체제에 맞는 native code로 변환, 만들어진 코드(native code)를 로드하여 프로그램을 실행시킨다(프로세서가 주어진 명령을 처리한다)

라이브러리란?

반복적인 코드 작성을 없애기 위해 만들어진 class나 function 들로 만들어진 것

// C#에서 만든 공용 라이브러리
Console.WriteLine();

// 내가 만든 라이브러리
int Sum(int a, int b)
{
return a + b;
}

int num = Sum(2, 3);
// 5

메모리 주소보기 디버그 > 창 > 메모리
&(객체) 적으면 찾을 수 있음

profile
고급 개발자가 되고 싶어요!

0개의 댓글