[C#] 공식문서 학습하기 2 : ~ 형식 시스템 / 개요

Sean·2024년 9월 20일
0

C# 학습

목록 보기
2/5

참고 자료: MS Document

  • C#이 생각보다 자료가 많이 없어서 그냥 공식문서 보고 정리하려고 한다.
    일단은 공식문서상의 구조를 따라가려고 하는데 내용은 내 맘대로 써질 예정
  • 본질이 iOS 개발자라 많이 비교하면서 학습을 진행할 예정이다.
    - @>--- 이런 기호와 함께 '기울임', '작게' 표시 되면 개인적인 생각이다.
  • 당연 이건 다른 언어를 했던 사람들이 C# 공부시 그나마 조금 편하라고 만들어 보는거지 아예 코드 짜는 사람이 처음이라면 이해 안될 수 있는다.
  • 이해 안되는건 뒤에 나올 내용이 앞에 나와서 그러는거니까 첨보는 단어면 일단 넘어가면 뒤에 다시 나온다.

형식시스템 / 개요

  • 결국에는 Type들에 대해서 설명할 부분이다.
int num = 5;
float fNum = 1.0f;
double dNum = 1.0;
bool flag = false;

1. 기본 제공 타입 종류

  • 값 형식 타입으로는 bool, int, float, double, char 같이 자주 쓰이는건 물론이고, byte, sbyte, decimal, uint, nint, lomg. ulong, short, ushort 같은 것들도 있다.
  • 참조 형식 타입으로는 object, string, dynamic 이 존재 한다.

2. 사용자가 직접 지정하는 타입

struct, class, interface, enum, record

.NET 클래스 라이브러리

  1. 명명 규칙
  • 계층 구조를 의미하는 스키마 이름을 지정하는데 . 구문을 사용
  • 이 방식을 사용하면 관련 형식을 네임스페이스로 그룹화해 보다 쉽게 검색, 참조 가능
    System.Collections.Generic.List< T > 이런 구문이 있다면 System.Collections.Generic 네임스페이스에 속하는 List< T >의 형식을 나타내는것
  1. System 네임스페이스
  • 위에서 타입들 나열해놨으니 그것들 확인해볼것

3.데이터 구조체

  • 다양한 데이터 구조 집합이 포함되어있는데 대부분 컬렉션이지만 다른 형태도 존재
    1. Array : idx 로 접근 가능한 개체 배열 -> 고정 적 크기 존재
    2. List : idx 로 접근 가능한 개체 목록 -> 크기가 자동 조정
    3. Dictionary<TKey,TValue> : 키를 통해 값에 엑세스 가능 -> 크기 자동 조정
    4. Uri : 개체 표현을 제공하며 URI 부분에 쉽게 엑세스 가능
    5. DateTime : 날짜와 시간으로 표시된 시간
      @>--- 뭐 이렇게 있다고는 하는데 대게 List랑 Dictonary를 자주 사용할 것 같다.(동적 크기 변경 짱..)
  1. 유틸리티 API
  • 여러 중요 작업 기능 제공하는 유틸리티 API 집합이 포함
    1. HttpClient : URI로 식별되는 리소스에서 HTTP 요청을 보내고 HTTP 응답을 받기 위함
    2. XDocument : XML 문서 로드, LINQ 사용해 쿼리하기 위함
    3. StreamReader / StreamWriter : 파일 읽기/ 쓰기

3. CTS (공용 형식 시스템)

  • .NET 타입에 대해서 2가지 기초 사항을 이해해야 함
    1. 상속원칙을 지원
      • 타입은 기본 타입에서 파생될 수 있고 기본 타입의 메서드, 속성 및 기타를 상속한다.
      • 마찬가지로 기본 형식이 다른 형식에서 파생될 수 도 있음
    2. 각 타입은 또는 참조 타입으로 정의
      • 이는 모든 사용자 지정 타입과 자체 사용자 정의 타입도 포함된다.
      • 둘(값 과 참조)은 컴파일 시간 규칙 및 런타임 동작이 다르다.

        사용되는 모든 타입들은 모두 System이라는 네임스페이스에 구성되어있는데
        타입들이 포함된 네임스페이스는 타입이 값인지 참조인지 관련은 없다.

  • 클래스(class) 및 구조체(struct)는 .NET CTS의 기본 구문 중 2가지인데, 각각은 기본적으로 하나의 논리 단위에 속하는 데이터 및 동작 집합을 캡슐화 하는 데이터 구조이다.
  • 데이터와 동작은 class, struct 또는 record의 멤버다.
    • 멤버는 메서드, 속성, 이벤트 등을 포함한다.
  • class, struct, record 선언은 런타임에 인스턴스 또는 개체를 만드는데 사용되는 청사진과도 같다.
  • Class 는 참조타입이다.
    • 이 타입은 개체가 만들어지면 개체가 할당되는 변수는 해당 메모리에 대한 참조만 보유한다.
    • 개체 참조가 새 변수에 할당되면 새 변수는 원래 개체를 나타낸다.
    • 모두 동일 한 데이터를 참조하므로 한 변수를 변경하면 다른 변수에도 반영된다.
  • Struct 는 타입이다.
    • 구조체가 만들어지면 해당 구조체가 할당되는 변수에 구조체의 실제 데이터가 포함된다.
    • 구조체를 새 변수에 할당하면 구조체가 복사된다.
    • 따라서 새 변수와 원래 변수에 동일한 데이터의 두가지 별도 복사본이 포함되므로, 한 복사본의 변경은 다른 복사본에 영향을 주지 않는다.
  • Record 는 일 수도 참조일 수도 있다.
  • 일반적으로 Class가 좀 더 복잡한 동작 모델링에 사용된다.
    • 일반적으로 클래스가 만들어진 후 수정 데이터를 저장한다.
  • Struct는 작은 데이터 구조에 가장 적합하다.
    • 일반적으로 구조체가 만들어진 후 수정하지 않을 데이터를 저장한다.

4. 값 형식

  • 값 형식에는 해당 값이 직접 포함된다.
  • 구조체의 메모리는 변수가 선언된 컨텍스트에서 인라인으로 할당된다.
  • 값 타입의 변수에 대한 별도의 힙 할당이나 가비지 수집 오버헤드는 없다.
  • record struct 형식을 선언할 수 있다.

struct & enum

  1. Struct
  • 구조체는 System.ValueType에서만 상속할 수 있기에 '사용자 정의 클래스 또는 구조체에서 상속하는 구조체'를 정의 할 수 없다.
  • 그러나, 구조체는 하나 이상의 인터페이스를 구현할 수 있다.
    • 구조체 형식을 구현하는 인터페이스 형식으로 캐스팅 할 수 있으며 이 캐스트로 인해 boxing 작업은 관리되는 힙의 참조 형식 개체 내에 구조체를 래핑
  • 일단 구조체는 맨 윗줄 이랑 값 타입이다! 라는게 중요함
  • struct키워드를 사용해 고유한 사용자 지정 값 타입을 만들며, 일반적으로 구조체는 다음과 같이 소규모 관련 변수 집합의 컨테이너로 사용한다.
    public struct Coords
    {
        public int x, y;
    
        public Coords(int x, int y)
        {
            this.x = x; 
            this.y = y;
        }
    }
  • [구조체 공식문서] 구조체의 공식 문서는 다음을 참조하면된다.
    @>--- 후에 이 또한 작성할 예정
  1. enum (열거형)
  • 열거형은 명명된 정수, 상수 집합을 정의한다.
    public enum DBMode
    {
        Create = 1,
        Read,
        Update,
        Delete,
    }
  • 위와 같이 작성하면 되는데 열거형 작성 방법은 Swift에서 하는거랑 비스무리 함
  • 그리고 구조체에 적용되는 모든 규칙이 열거형에도 적용이 되며 [열거형 공식문서]를 참조 할 것

5. 참조 형식

  • class, record, delegate, 배열, interfacer 로 정의된 형식이다.

  • 해당 참조 타입의 변수 선언시에는 해당 타입의 인스턴스를 할당하거나 new 연산자를 사용해 생성할 때까지 값 null을 포함한다.

  • 참조 형식은 상속을 완벽하게 지원하는데 class 만들 떄 sealed로 정의되지 않은 기타 인터페이스 또는 클래스에 상속할 수 있다.
    @>--- 이게 뭔소리일까.. 알아봄 (이해 안되면 일단 넘겨도 됨)***

  • 클래스, 구조체, 레코드 공식문서 는 여길 참고

  • 상속은 공식문서 여길 참고

    class 만들 떄 sealed로 정의되지 않은 기타 인터페이스 또는 클래스에 상속할 수 있다?
    @>--- 이게 무슨 소리일까 한 번 고민해봅시다.

    • 일단 참조 타입은 class, interface, delegate, array, string을 포함하며 참조형식은 힙에 저장되며, 변수는 해당 객체의 메모리 주소를 가진다.
    • 참조 타입은 상속을 완벽하게 지원하며 C#의 경우에는 단일 상속을 지원한다. 즉, child 는 1 parent 만 상속할 수 있다.
      • 하지만, 인터페이스는 다중 상속이 가능하며, 틀래스는 여러개의 인터페이스를 구현할 수 있다.
    • 그래서 참조 타입은 상속을 통해 기능의 확장과 다형성을 구현할 수 있다.

    그럼 sealed 키워드는 뭘까?

    • 해당 키워드는 클래스 선언 앞에 사용되어, 해당 클래스를 더 이상 상속할 수 없도록 한다.

    • 즉, 해당 키워드로 선언된 클래스는 상속의 마지막 단계로 다른 클래스가 이를 상속받아 확장할 수 없다.

      public sealed class FinalClass { }
      • 근데 해당 키워드는 메서드나 속성 앞에서도 사용할 수 있으며, 이때는 상속 받은 클래스에서 해당 메서드를 재정의 할 수 없게 한다.
      • 이때는 override 키워드와 함께 사용해야 한다.
      • virtual 키워드는 해당 멤버가 child 클래스에서 override 될 수 있음을 나타낸다.
        • 즉 기본 클래스에서 선언된 멤버를 자식 클래스에서 재정의해 다형성을 구현할 수 있게 하는 중요한 키워드이다.
        • 해당 키워드를 사용하는 멤버는 기본적으로 기본 클래스에서 구현되어 있어야 한다.
        • 그냥 public virtual void Show(); 이렇게 넘기면 안됨!!!
      • 구현 예시는 아래와 같다.
      class BaseClass
      {
          public virtual void Show()
          {
              Console.WriteLine("Hello World");
          }
      }
      
      class DerivedClass : BaseClass
      {
          public sealed override void Show()
          {
              Console.WriteLine("Hello SUB World");
          }
      }
      
      class SubDerivedClass: DerivedClass
      {
          // Show 메서드 재정의 불가
      }

    근데 여기서 override 키워드가 나온 김에 다른 키워드를 하나 더 보자면 abstract가 있다.

    • 이 키워드는 virtual 메서드와 비교를 해보면 된다.

    • virtual 키워드 같은 경우에는 기본 클래스에서 구현을 해야지 사용이 가능한데 이 키워드로 선언된 메서드는 기본 구현이 필요 없다.

      • 단, 이렇게 선언된 메서드의 경우 이 메서드의 클래스를 상속한 자식 클래스는 반드시 해당 메서드를 구현해야 한다.
        • 마치 인터페이스의 디폴트 구현 안된 메서드와 같은 기능같다.
    • 구현 예시는 위에서 구현했던 코드들을 재활용해본다.

      public abstract class BaseClass
      {
          public virtual void Show()
          {
              Console.WriteLine("Hello World");
          }
          public abstract void Log(string msg);
      }
      
      class DerivedClass : BaseClass
      {
          public sealed override void Show()
          {
              Console.WriteLine("Hello SUB World");
          }
          public override void Log(string msg)
          {
              Console.WriteLine($"{msg}");
          }
      }
      
      class SubDerivedClass: DerivedClass
      {
          // Show 메서드 재정의 불가
      }

Class

  • 클래스 선언
    @>--- 당연 내부 설정 로직은 후에 기술할 예정
    class MyClass
    {
    	public int classNum2 = 2;
      public int classNum { set; get; }
    }
  • 위와 같은 클래스가 있다고 가정했을 떄 이를 생성 및 할당하는 방법을 보자면 다음과 같다.
    MyClass myClass = new MyClass();
    MyClass myClass2 = myClass;

Interface

  • interface는 new 연산자를 사용해 직접 인스턴스화할 수 없다
  • 대신 인터페이스를 구현하는 클래스의 인스턴스를 만들고 할당한다.
  • 인터페이스는 구현해야 하는 메서드, 프로퍼티, 이벤트 등 멤버들의 계약(@>--- contract 라고 하는데 제약이지 않을까..)을 정의하는 역할을 한다.
    • 이를 통해 코드의 유연성, 확장성, 재사용성을 높일 수 있다.

근데 이거 개념적으로는 swift에서 protocol이랑 비슷한거 같다.

  • 그렇게 생각하는 이유로는 인터페이스의 경우에는 하나 이상의 추상적인 요소들을 포함하는 추상적인 타입이라는 정의와
  • 클래스나 구조체가 특정 기능을 구현하도록 강제 하고
  • 인스턴스 할 수 없기에 인터페이스 자체의 객체를 생성할 수 없고
  • 모든 멤버들이 일단 public이며(다 구현해야 한다는 것), 구현부가 없다.
    • 근데 이거도 보니까 C# 8.0 부터는 기본 값을 넣을 수 있음
    • 그렇다는거는 이걸 구현하는게 강제 된다는게 아님
      • 만약 int Add(int a, int b); 를 인터페이스로 넣어놨는데 만약 디폴트 구현을 안해놨으면 이 인터페이스를 사용하는 클래스에서 해당 함수들을 계속 구현해야 하는데..
        디폴트로 return a+b 를 구현해두면 매번 구현 할 필요 없이 저 동작을 할 수 있게 된다.
  • 다중 상속이 되기에 클래스나 구조체는 여러 개의 인터페이스를 구현할 수 있다.

그런고로 Interface == Protocol 이라는 생각을 한다.

  • 구현은 다음과 같다.

    public interface IMyInterface
    {
        void Log(string msg);
        void LogError(string error);
    }
    
    class InterfaceClass : IMyInterface
    {
        public void Log(string msg)
        {
            Console.WriteLine($"{msg}");
        }
    
        public void LogError(string error)
        {
            Console.WriteLine($"{error}");
        }
    }
    
    class Program
    {
        static void Main(string[] args)
        {
            IMyInterface myInterface = new InterfaceClass();
        }
    }
  • 개체가 만들어지면 관리되는 힙에 메모리가 할당되고, 변수는 개체의 위치에 대한 참조만 포함한다.
    • 관리되는 힙의 형식은 할당될 때, 회수될 때 모두 오버헤드가 필요하다.
    • 가비지 수집은 회수를 수행하는 자동 메모리 관리 기능으로 고도로 최적화되고 대부분에서 성능 문제를 일으키지 않는다.

배열

  • 모든 배열은 해당 요소가 값 형식이어도 값 타입이 아니고 참조 타입이다.
    int[] nums = [1,2,3,4,5];

6. 리터럴 값 타입

  • 아래 설명 보세요.

    리터럴(Literal) 이란??

    • 코드에서 직접적으로 값을 표현하는 고정된 값을 말한다.
      즉, 프로그램 실행 중 변하지 않는 상수값을 의미한다.
      근데 이게 진짜 상수를 의미한다기보다는 어떤 데이터 타입인지에 따라 다르게 처리된다.
      즉, 각 리터럴은 기본적으로 자신의 데이터 형식을 갖고 있고 필요에 따라 명시적으로 지정할 수 있다.
    • 그니까 이게 뭔소리냐면... 변수 생성 할떄 값 넣을때 그거! 그게 리터럴 값이라는 소리다
    int num = 1;
    long lNum = 1L;
    uint uNum = 1U;
    double dNum = 1.0;
    float fNum = 1.0f;
    string str = "abc";
    char chr = 'abc';
    bool flag = true;
    string s = "The answer is " + 5.ToString();
    Type type = 12345.GetType();

7. 제네릭 타입

  • 실제 형식에 대한 자리 표시자로 사용되는 하나 이상의 형식 매개 변수를 사용해 형식을 선언할 수 있다.
  • 인스턴스를 만들 때 구체적 형식을 제공하며 해당 형식을 제네릭 형식이라 한다.
  • 형식 매개 변수를 사용하면 각 요소를 개체로 변환 할 필요 없이 같은 클래스를 재사용해 요소 형식을 포함할 수 있다.
  • 이건 좀 써보면서 익숙해지기 전까지는 이게 왜 필요한가 싶으니까 한 번 씩 사용에 대해서 고민 해보면 된다.
  • 이건 후술 필요시, 제네릭 공식문서 참조
    public T outT<T>(T num)
    {
        return num;
    }

    제네릭 타입
    이건 swift에도 있는 그 제네릭 생각하면 된다.

8. 암시적 형식, 무명 형식 및 nullable 값 형식

  • var 키워드를 사용해 클래스 멤버가 아닌 로컬 변수를 암시적으로 형식화할 수 있다.

var 키워드

  • var 키워드의 경우에는 프로그램이 변수나 상수 선언시 형식을 유추해서 저장을 할 수 있게 하는 키워드이다.
  • 전역변수로 생성할 수 없으며 지역변수로 class나 메서드 내부에서 생성을 해서 사용 할 수 밖에 없다.
  • 당연하게도 형식 추론을 해야 하는 키워드 이므로 성능에는 좋지 않은 결과를 만들어 잦은 사용은 좋지 못하다
  • 만능처럼 보일 수 는 있는데 너무 자주 쓰면 휴먼에러도 발생할 수 있고 좋지는 않으나 쓸 수 밖에 없거나 쓰는게 좋은 경우도 당연하게 있다.
// 이렇게 할것을
List<int> list = new List<int>();
// 이렇게 할 수 있다.
var list2 = new List<int>();
  • 형식 추론이 나쁜 이유가 추론을 해야하니까 그 추론을 하기 위한 작업이 따로 들어가야 해서 그 동작에 대한 시간이 걸리니 응용프로그램이 느려지거나,
  • 그에 따른 이슈가 발생하는건데 위에 처럼 애초에 타입을 명시하지 않지만 추론으로 확정하는 방법을 사용하면 꽤 보는게 편해질 수 있다.
  • var 의 사용을 남용하는건 좋지 않지만 적절히 힌트를 줘가면서 사용하는건 나쁘지 않아 보인다.

당연하게도 이런건 swift에도 비슷한 화제가 나온적이 있다.

nullable 값 형식

  • 일반적인 값 형식은 null을 가질 수 없다
  • 그러나!! 형식 뒤에 ?를 추가하면 null 허용 값 타입을 만들 수 있다.
  • 값이 null 일 수 있는 DB의 데이터 전달 같은거 하거나 다른 상황에서 유용하다.
  • 공식문서를 참조

9. 컴파일 시간 형식 및 런타임 형식

  • 변수의 컴파일 시간과 런타임 형식은 서로 다를 수 있다.

  • 이게 컴파일 시간 형식은 소스 코드에서 선언되거나 유추되는 변수의 형식인데, 런타임 형식은 해당 변수에서 참조하는 인스턴스의 형식이다.

    string msg = "Hello My name is SEAN";
    IEnumerable<char> someChar = "Hello My name is SEAN";
  • 위의 코드에서 보면 두 변수의 런타임 형식은 전부 string 인데, 컴파일 시간은 처음은 object, 두번째 줄은 IEnumerable 이다.

  • 두 형식이 변수에 대해 다른 경우 컴파일 시간 형식과 런타임 형식이 적용되는 경우를 이해 하는 것이 더 중요하다.

    • 컴파일 시간 형식에 따라 컴파일러가 수행하는 모든 작업이 결정된다.

    @>--- 근데 이거 좀 코드 짜는데 딱히 막 이렇게까지 따져서 하기에는 지금 할건 아닌거 같으니 여기까지..

profile
"잘 할 수 있을까?"를 고민하기보단 재밌어 보이는건 일단 하고, 잘하기 위해 그냥 계속합니다.

0개의 댓글