[C#] 공식문서 학습하기 4 : ~형식시스템 / 클래스

Sean·2024년 9월 20일
0

C# 학습

목록 보기
4/5

참고 자료: MS Document

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

형식시스템 / 클래스

참조형식

  • 클래스로 정의된 형식은 참조 형식이다.
  • 런타임에 참조 형식의 변수 선언할 때 변수는 new 연산자를 사용해 클래스의 인스턴스를 명시적으로 만들거나, 다른 곳에서 생성되었을 수 있는 호환되는 형식의 개체를 할당할 때까지 null을 포함한다.
    • = new로 인스턴스 생성하고 값 안 넣으면 null이다.
  • 개체가 만들어지면 해당 개체에 대해 관리되는 힙에 메모리가 할당되고 변수에는 개체 위치에 대한 참조만 포함된다.

생성자 및 초기화

  • 인스턴스를 만들 떄 해당 필드와 속성이 유용한 값으로 초기화하는 방법에는 여러 가지가 있다.

    • 기본값 설정
    • 필드 init
    • 생성자 매개 변수
    • 개체 init
  • 모든 .NET 형식에는 디폴트가 있는데 숫자 형식은 0이고 모든 참조 형식은 null이다.

  • 기본값이 올바른 값이 아닌 경우 초기값을 설정 할 수 있다.

    public class Container
    {
        private int _capacity = 10;
    }
  • 호출자가 초기 값을 설정하는 생성자를 정의해 초기 값을 제공하게 요구할 수 있다.

    public class Container
    {
        private int _capacity;
    
        public Container(int capacity) => _capacity = capacity;
    }

    => : 이게 뭘까?

    • 해당 연산자는 람다(Lamda)식에서 사용되는 람다 연산자 또는 화살표 연산자라고 불린다.
    • 람다 식본문 멤버 를 정의할 때 사용한다.

    1 . 람다식에서의 사용: 람다 연산자
    람다식은 익명 함수를 표현하는 간결한 방법으로 메서드나 대라지를 정의하지 않고도 함수를 전달할 수 있다.

    • 사용방법은 (입력 매개변수) => 식 또는 문장 블록 이런식으로 하며 사용 방법은 2가지가 있다.
      • 이렇게 표기할때 입력 매개 변수가 없으면 () => 식 또는 문장 블록으로 표현하면 된다.
    static void Main(string[] args)
    {
        var nums = new List<int> { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 };
        List<int> search = nums.FindAll(x => x % 2 == 0);
        foreach (int n in search)
            Console.WriteLine(n);
    }
    • 람다식은 뭔가 함수를 새로 지정해서 해당 동작을 하는 그런 로직을 짜기에는 코드가 길어져 바로바로 확인이 안되거나 진짜 간단 로직 + 1회성인걸 굳이 함수까지 만들 필요 없을때 쓰면 좋을것으로 보인다.
    • 해당 코드 작성은 조금 더 익숙해져야 충분히 가능하겠지만 그런게 아니라면 초콤 문법 상으로 헷갈릴 여지가 있어보인다.
    Func<int,int,int> check = (x,y) => { return x*y; };

    @>--- 자주 이용할 것 같은 람다식 구현 방법

    2 . Expression-bodied member (식 본문 멤버(?))

    • 일단 예제 코드를 먼저 봐보자
    public static int Add(int x, int y) => x + y;
    • 뭔가 람다식과 비슷한 모습을 보이는것처럼 보이는데 엄밀히 말하면 완전 다른 개념이다.
    • 이 문법은 메서드, 속성, 생성자, 소멸자 등을 간결하게 표현하기 위해 사용된다.
    • 해당 문법은 메서드나 속성의 본문을 간단한 식(expression)으로 대체하는 문법입니다.
      • 메서드의 본문한 줄로 간단히 표현될 때 =>를 사용해 이를 간결하게 나타낼 수 있습니다.

    @>--- 이게 뭔뜻이냐면 얘는 메서드라는거다.
    그렇다는건, Main 함수 내에서 선언하고 바로 사용하고 메모리 날리고가 안된다는 소리다.

    정리

    1. 람다식 = 익명 함수 정의에 사용 + 주로 대리자(delegate)나 LINQ에서 함수형 프로그래밍을 지원하기 위해 활용
    2. Expression-bodied member = 간결하게 표현하는 방법으로 주로 코드의 가독성을 높이고 간단한 메서드를 축약할 때 사용
      @>--- 약간 swift와 obj-C의 클로저나 블록과도 비슷한 느낌으로 보면 이해하기 쉬울것으로 보인다.

    클래스 생성과 동시에 초기화 시키기

    • 일단 예제 코드를 먼저 작성해보자
    class Program
    {
        static void Main(string[] args)
        {
             TestClass tcl = new TestClass();
             Console.WriteLine(tcl.ReadX());
        }
    }
    
    class TestClass
    {
        private int x;
        public TestClass() => x = 3;
        public int ReadX() => x;
    }
    • 여기서 눈여겨 볼 곳은 TestClas클래스의 TestClass 메서드 이다.
      • 사용하는 방법은 매우 간단하다. 몇가지 조건만 맞다면 애용하게 될 방법이다.
        1] 클래스를 코드상에 입력할때는 그 값을 정하지 않는 즉, 동적인 변수가 필요로 할때 해당 변수의 값을 초기화 시킬떄
        2] 해당 클래스가 처음 인스턴스로 할당될 때 해당 값이 초기화 되었으면 할때 사용하면 된다.

      • 위의 조건들 외에도 여러 조건이 있겠지만 그걸 떠나 그냥 클래스 초기화 할때 쓴다 생각하믄 된다.
      • 사용방법 또한 매우 쉬운데 public 클래스_이름(매개변수) = {초기화 구문} 이런식으로 클래스의 이름과 똑같은 메서드를 만들어 뒤에 초기화 구문을 넣어주면 된다.
      • 예시 코드에서는 직접값을 넣어주고 있지만, 클래스 이름 뒤의 매개변수 넣는곳에 매개변수를 삽입해서 클래스 외부에서 변수의 값을 지정해줄 수 도 있다.
      • 그리고 C# 12에서 나온건데 아래처럼 직접 클래스 명에서 변수 때려서 하는것도 있는데 개인적으로는 전자의 방법을 더 애용할 것 같다.
        class TestClass(int inx)
        {
            private int x = inX;
            public int ReadX() => x;
        }
  • 속성에서 required 한정자를 사용하고 호출자가 개체 이니셜라이저를 사용하여 속성의 초기 값을 설정하도록 허용할 수도 있습니다.
    - 이 키워드를 사용해서 생성된 변수는! 무조건 초기화 작업을 해야 한다는것을 의미함 new 클래스() 하고 뒤에 {} 이렇게 해서!!

  • 예시 코드는 다음과 같다.

    class Program
      {
          static void Main(string[] args)
          {
              TestClass tcl = new TestClass() { y = 3 };
          }
      }
    
    class TestClass
    {
        public required int y { get; set; }
    }

클래스 상속

  • 클래스는 OOP의 기본적인 특성인 상속을 완전히 지원한다.
  • 클래스 생성 시 sealed로 정의디지 않은 다른 클래스에서 상속할 수 있다.
  • 다른 클래스는 클래스에서 상속하고 클래스 가상 메서드를 재정의할 수 있으며 하나 이상의 인터페이스를 구현할 수 있다.
  • 상속은 파생을 통해 수행된다.
    @>--- 공식문서를 보다보면 상속에서 기본,파생을 나눠서 설명을 한다.
    기본의 경우에는 가장 윗단 즉 트리의 루트 노드 같은 형식을 이야기 한다.
    파생의 경우에는 기본을 상속하는 모든 형식들을 이야기 한다.
    그래서 어떤 파생은 무조건 자식이 아닐 수도 있다. (이 파생을 상속하는 다른 파생이 있으면 부모이자 자식이 되는거니까)
    • 즉, 데이터와 동작을 상속하는 소스 기본 클래스를 사용해 선언된다.
    • 파생 클래스 이름 뒤에 콜론(:) 과 상속하는 기본 클래스 이름을 추가해 상속 기본 클래스를 지정한다.
    • 기본 클래스의 초기화('본문에서는 기본 클래스가 포함된 경우'라고 써져있다.) 생성자를 제외하고 모든 멤버를 상속한다.
  • 예시 코드는 다음과 같다.
    public class Sean: Person { ... }
  • 하나의 기본 클래스에서만 직접 상속할 수 있다.
    • 그러나 기본 클래스가 다른 클래스에서 상속될 수 있으므로 여러 클래스를 간접 상속 가능하다.
  • 클래스는 하나 이상의 인터페이스를 직접 구현할 수 있다.
    @>--- abstract과 sealed 에 관한 부분은 이미 서술했다.
  • 클래스 정의는 여러 소스 파일로 분할 될 수 있고 자세한 건 Partial 클래스 및 메서드 공식문서를 참조하면 된다.

클래스의 상속과 인터페이스

  • swift와 다르게 C#에서의 클래스는 하나의 상속만 가능하기에 이를 조금이나마 따라하는 작업으로 인터페이스를 사용한다.
  • 여러 인터페이스를 구현함으로 다중 상속의 일부 기능을 흉내낼 수 있다.
    중요한 키워드는 일부, 흉내 이다.

왜 일부 흉내일까?

  • 상속과 인터페이스로 구현했을때의 코드 차이를 예시로 보여주겠다.

    public class Animal
     {
         public void Eat()
         {
             Console.WriteLine("eating...");
         }
     }
    
     public class Dog : Animal
     {
         public void NextAction()
         {
             Eat();
             Console.WriteLine("next...");
         }
     }
    public interface Bird
    {
        void Fly();
    }
    
    public interface Carnivore
    {
        void WantMeat();
    }
    
    public class Eagle : Animal, Bird, Carnivore
    {
        public void Fly()
        {
            Console.WriteLine("flying...");
        }
    
        public void WantMeat()
        {
            Console.WriteLine("Want Meat...");
        }
          
        public void NextAction()
        {
            Fly();
            Eat();
            WantMeat();
            Console.WriteLine("next...");
        }
    }
  • 상속은 애초에 부모의 모든것을 가져오기에 부모가 구현해둔 로직까지 다 가져올 수 있지만

  • 인터페이스의 경우에는 그렇다기 보다는 그냥 청사진을 그려주는 역할밖에 못하기에 해당 로직은 선택한 클래스에서 구현을 해줘야 한다.

  • 그렇기에 의존성이나 관계도 또는 코드의 이해를 위하는 방법은 둘이 비슷할지 언정 정작 내부 디테일적으로 보게 되면 완벽하게 다른걸 알 수 있다.

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

0개의 댓글