[More Effective C#] 1.1 Working with Data Types

yunicorn·2021년 2월 21일
0

More Effective CSharp

목록 보기
1/1

들어가는 말

저 같은 경우는 C++으로 코딩을 주로 하다가 팀을 바꾸며 C#으로 코딩을 하게 됨으로서, 큰 learning curve없이 시작할 수 있었던 것 같습니다. 하지만 시간이 갈 수록, ‘이렇게 적는 것이 좋은 C# 코드일까?’ , ‘더 효과적인(effective) C# 방식으로 같은 로직을 짜는 방법은 무엇일까 ?’ 하는 생각들이 들기 시작했습니다.

C# 언어에 대한 공부보다는 이 언어를 좀 더 enterprise level product development 에서 더 잘 쓸 수 있는 방법에 대해서 배우고 싶어서, C# 사용법에 관한 리소스를 찾아보았습니다. 자바에서는 ‘Effective Java’라는 유명한 책이 바로 이러한 니즈를 채워준다고 어림풋이 알고 있어서, C#에서 Effective Java 같은 역할을 해주는 책이 무엇인지 조사해보던 결과 ‘More Effective C#’이라는 책을 접하게 되었습니다.

운이 좋게도 회사 온라인 도서관에서 Ebook으로 책을 무료로 볼 수 있을 뿐만 아니라 소유할 수 있어서, 이 책을 제 개인 공부시간에 조금씩 읽어보면서 배운 내용들과 기억하고픈 내용들을 노트 형식으로 정리해보기로 했습니다.

같은 저자가 쓴 ‘Effective C#’이라는 책도 있는데요, 이 책은 회사 도서관에 있는 버전으로는 C# 4.0버전을 바탕으로 설명이 들어가있어서, 이왕이면 제가 필요로한 C# 버전들을 커버해주는 ‘More Effective C#’으로 공부를 하기로 결정했습니다.

이번 포스트에서는 책의 첫 인트로 부분과 제 1장 1 섹션을 읽고 배웠던 부분들을 가볍게 정리해보았습니다.

글의 목적:

  • 글이 목적은 ‘More Effective C#’ 책을 읽고 배운 내용들을 노트형식으로 정리해 제가 나중에 쉽게 정보를 찾고, 찾은 정보를 빠르게 이해할할 수 있도록 하기 위함입니다 .
    • 단순히 기술적인 부분 뿐 아니라, 작가의 관점 중에 마음에 드는 부분이 있으면 그 quote 또한 정리할 예정입니다.
  • 비록 제 1 독자는 제 자신이지만, 저와 비슷한 니즈를 가지고 계신 다른 독자분들에게도 도움이 된다면 제가 목적한 바를 이루는 일이라서 더할나위 없이 기분 좋을 것 같습니다 :)

알림:

  • 원서를 읽고 생각을 정리하는 것이라서, 기술적 용어를 한국어로 번역함에 있어 오역이 있을 수 도 있음에 양해를 미리 부탁드립니다.
  • 오역을 방지하고 제 자신이 조금 더 효과적으로 글 작성을 하기 위해 많은 부분들은 번역 없이 쓸 경우가 있을 수 있습니다. 한국어와 영어가 많이 섞여있어서 일관성이 조금 없어보일 수 있는 점에 양해를 미리 부탁드립니다.
  • 글에서 나오는 예제 코드들은 책에서 나오는 코드이거나 그 코드들을 바탕으로 조금 변형한 저의 코드입니다. 책에서 나오는 코드를 그대로 쓸 경우에는 (*) 표시가 되어있습니다.
  • 코드들은 바로 사용 가능한 코드들이 아니라 글의 길이와 가독성을 위해 중요한 포인트들만 남기고, 실제로 컴파일되고 작동하거나, exception handling을 하는 등의 로직은 구현되어 있지 않음을 알려드립니다.

Notes

Introduction

책의 목적과 주 독자

  • 이 책은 C# 버전 7.0가 support 하는 다양한 features들에 대한 책이 아니라, 이러한 features들을 사용해서 개발 환경에서 자주 마주하는 문제들을 어떻게 해결할 수 있는지에 관한 굉장히 실용적인 조언들을 주기 위함입니다.
  • 책의 주 독자:
    • C#을 주 언어로 사용하는 professional developer

Chapter 1: Working with Data Types

제 1장의 목적:

  • 데이터를 프로세스하거나 핸들링하는 매소드로부터 분리시키는 법에 대해서 설명하고 있습니다.

1.1: Public Data Member말고 Property 사용하기.

Property란?

  • C#의 어떻게 보면 굉장히 유니크한 feature라고도 볼 수 있습니다. Public Data Member, 즉 public field처럼 생겼지만, 실은 data member라 아니라 스페셜한 매소드인 accessors입니다.

  • 예제:

    class Customer
    {
        private string name;  // private field
        public string Name {
            get { return name;  }
            set { name = "Totoro";  }
        } // 여기서 `Name`이 property 입니다.
    }

C# Rule 1:

  • Public Data Member (또는 public field라고도 부를 수 있음) 를 사용해서 data를 외부에 directly expose하거나 또는 public getter 또는 setter를 써서 expose하지 말고, 늘 property를 쓰기.
  • Class나 struct에서 선언(declare)하는 변수들 (fields)은 public이 아니라 private이나 protected accessibility만 갖도록 하기.

Property를 쓰면 좋은 점은 무엇일까?

C#으로 작성을 하면서 늘 궁금했던 점이기도 했습니다. 도대체 왜 property라는 색다른 컨셉을 만들었을까에 대한 질문이었는데요, 즉 property가 주는 장점에 대해서 이해를 하고 싶었습니다. 이 책에서 장점들에 대해서 굉장히 잘 설명해 주고 있습니다.

(1) Data Encapsulation.

  • OOP에서 가장 중요한 원칙인 data encapsulation을 property를 통해 이룰 수 있습니다.
    • 예를 들어, Customer라는 클래스 안에서, Name 이라는 public field를 해놓고, 모든 caller들이 이 Name 필드의 value를 set 할 수 있게 하면, 이 클래스의 implementation을 사용자들에게 노출시키는 경우로, data encapsulation 원칙을 지키지 않게 되는 굉장히 bad practice 라고 볼 수 있습니다.
    • Property를 사용하면, 클래스의 caller들은 public getter 와 setter를 사용해서 데이터를 가지거나 set할 수 있기 때문에, implementation details을 사용자들/callers에게 노출시키지 않고 value 값을 돌려주거나 정할 수 있습니다.

(2) Method 가지고 있는 이점들 사용 가능

  • Methods이기 때문에 verification logic을 쉽게 넣을 수 있습니다.
  • 예를 들어 Customer의 Name value의 getter , setter를 다음과 같은 additional logic을 가지도록 구현할 수 있습니다.
class Customer
{
    private string name;
    public string Name {
      get 
      {
          if (String.IsNullOrEmpty(name))
          {
             return "default value";
          }
          return name;
       }
      set 
      {
         if (string.IsNullOrEmpty(value))
            throw new ArgumentException("Name cannot be blank", nameof(Name));
         name = value;
      }
   }
}
  • Multithreading support도 쉽게 넣을 수 있습니다.
    • 코드 예제 (*)
   public class Customer
   {
       private object syncHandle = new object();
       private string name;
       public string Name
       {
         get {
          lock (syncHandle)
          return name;
         }
         set {
            if (string.IsNullOrEmpty(value))
              throw new ArgumentException( "Name cannot be blank", nameof(Name));
            lock (syncHandle)
            name = value;
         }
      }
      ....
  }
  • Virtual Property 도 가능합니다.
    • Base class에서 정의된 properties는 derived 클래스에서 다르게 정의 될 수 있습니다.
    • 코드 예제 (*)
public class Customer
{
    public virtual string Name
    {
       get;
       set;
    }
  }
  • interface에서 abstract property로 사용할 수 있습니다.
    • 코드 예제(*)
public interface INameValuePair<T>
{
       string Name { get; }
       T Value { get; set; }
}

(3) 사용자들은 public fields를 사용하듯이 사용 가능.

  • Properties를 가지고 있는 object을 사용하는 caller는 public data member를 get/set할 때처럼 사용할 수 있습니다.
  • 코드 예제:
    class Program
    {
        static void Main(string[] args)
        {
            Customer customer = new Customer();
            customer.Name = "torotoro";
            Console.Write(customer.Name);
        }
    }

(4) .Net Framework에서는 public data members가 아닌 properties를 사용할 것이라는 가정을 바탕으로 만들어짐.

  • .Net Framework에서 제공하는 data binding 라이브러리들은 public data members 는 서포트하지 않고 오직 properties만 서포트합니다.
  • 이 부분에 대해서는 제가 아직 잘 몰라서, . Net Framework에서 data binding에 대해서 조금 더 배울 기회가 생긴다면, 그 때 다시 좋은 예제들과 함께 글을 쓰도록 하겠습니다. 지금은 이렇게 .Net Framework에서는 properties만 가능한 기능들이 있다 정도로만 알고 넘어가겠습니다.

(5) Concise syntax로 표현 가능.

  • Auto Property
    • 많은 경우에, getter와 setter에 더 추가된 로직 없이 그냥 있는 그대로의 field를 get하거나 set하는 경우입니다.
    • 이 같은 경우에, C#은 auto property라고 하여 다음과 같이 property를 정의할 수 있습니다.
   class Customer
   {
        public string Name { get; set; } //auto property
   }
  • 컴파일이 private field (또는 backing store라고도 불림)를 만들어서 당연한 getter, setter로직을 구현해줍니다.
  • Expression bodied properties
    • Property가 single expression으로 구현이 될 때, C#의 expression body 기능을 사용해서 다음과 같이 간단하게 property를 나타낼 수 있습니다.
    • 코드 예제: Microsoft Expresssion-Bodied members: Property
public class Location
{
    private string locationName;
    public Location(string name) => Name = name; // expression-bodied property
    public string Name
    {
       get => locationName;
       set => locationName = value;
    } // expression-bodied property
}   

(6) Indexer

  • Indexer란 Class나 Struct를 array 처럼 인덱스되서 사용할 수 있도록 해주는 property인데요, 기본 property와 다르게 indexer는 parameter를 받기 때문에 parameterized property라고도 불립니다.
  • 코드예제:
    class CustomerNames<T>
    {
        private List<T> names = new List<T>();
        // indexer that allows client code to use [] notation
        public T this[int i]
        {
            get => names[i];
            set => names.Insert(i, value);
        }
    }
class Program
{
    static void Main(string[] args)
    {
       //using indexer
       var customerNames = new CustomerNames<string>();
       customerNames[0] = "Totoro Peng";
       Console.WriteLine(customerNames[0]); // print out 'Totoro Peng"
   }
}

(7) 코드 변경을 해야 할 경우, 쉽게 변경 가능.

  • 시간이 지남에 따라 새로운 requirement가 생기거나 로직에 수정을 해야할 경우에, 쉽게 변경할 수 있습니다.
    • 예를 들어, 처음에는 Customer라는 클래스안에서 Name field를 set하는 경우 모든 value가 다 가능했는데, 나중에는 empty string이거나 null인 value로 set을 할 경우 exception을 던지기로 하는 경우로 바꾸었다고 가정해보겠습니다.
    • 이러한 경우에 property의 set function을 쉽게 바꾸어서 이러한 새로운 요구를 해결 할 수 있습니다. (below example)
  • Original Code:
class Customer
{
   public string Name { get; set; } //auto property
}
  • Later Code:
class Customer
{
    private string name;
    public string Name {
       get {
              if (String.IsNullOrEmpty(name))
              {
                 return "default value";
              }
              return name;
           }
       set {
               if (string.IsNullOrEmpty(value))
                   throw new ArgumentException("Name cannot be blank", nameof(Name));
               name = value;
            }
    }
 }
  • 여기서 잠깐!
    • ‘처음에는 public data member로 define했다가 나중에 setter function 넣어도 별 다를 게 없을 것 같은데?’ 라는 생각이 들 수도 있는데, 작가가 public data member-> property로 바꾸는 것이 source code만 바꾸면 되는 것이 아니라는 아주 중요한 포인트를 짚어줍니다.
    • Public data member나 property 둘 다 모두 코드 레벨 (source level)에서는 호환되는 것(compatible) 처럼 보이지만, binary level에서는 호환되지 않습니다.
    • 이 말은 즉, 코드를 컴파일하게 되면 나오는 artifact중에 하나인 Microsoft Intermediate Language (MSIL) (또는 Common Intermediate Language (CIL))가 data member인 경우와 property일 경우 서로 다른 instruction을 가지고 있다는 뜻입니다.
    • 이 뜻은, public data member를 상응하는 property로 바꾸게 되면, 이 public data member를 사용했던 모든 코드를 다시 컴파일(recompile) 해야 합니다. 코드가 적은 경우이면 문제가 없을지 몰라도, 코드베이스가 엄청 큰 경우에는, public data member 레퍼런스가 있는 모든 코드를 다시 컴파일 해야 한다면, 굉장히 비효율적으로 시간을 사용하게 되는 결과를 초래합니다.

이러한 이유들 때문에, C#에서는 public data member 는 사용하지 말고 property를 사용하라는 룰이 있습니다.

하지만 property는 method이고 public data member는 데이터이기 때문에 property를 사용하였을 때, performance적으로는 문제가 없는지에 대한 궁금증이 생겼는데요, 이 책에서 이에 대한 답 또한 제시해 주었습니다.

JIT compiler를 사용하는 runtime environment (ex: JVM, .Net Framework)라면 JIT compiler가 property accessors같은 몇 개의 methods들은 inline하기 때문에 퍼포먼스에 차이가 없다고 말합니다. JIT compiler를 사용하지 않는 runtime environment 경우에도 퍼포먼스 차이가 굉장히 미미해서, 문제가 되지 않는다고 설명해주고 있습니다.

저도 회사에서 코드를 하면서 ‘굳이 왜 property?’ 라는 궁금증을 가진 적이 있었는데, 이 섹션을 읽으면서 이 질문에 대한 답을 조금 더 깊이있게 이해하고 공부할 수 있어서 재미있게 읽을 수 있었습니다.

profile
해외에서 개발 활동을 하고 있습니다 :)

0개의 댓글