[이것이 C#이다] 9. 프로퍼티

ssu_hyun·2022년 4월 15일
1

C#

목록 보기
13/22

Key point

  • 프로퍼티
  • 메소드, 필드, 프로퍼티의 차이
  • 프로퍼티를 통한 객체 초기화
  • 무명 형식
  • 인터페이스와 추상 클래스에서의 프로퍼티 선언 방법

9.1 프로퍼티(Property)

  • public 필드를 다루듯 내부 필드에 접근하게 해주는 멤버
    • get 접근자: 내부 필드 값을 읽고 외부로 출력할 때
    • set 접근자: 내부 필드에 새로운 값을 할당/입력할 때
  • 선언
    class MyClass
    {
        private int myField;
        public int MyField
        {
            get
            {
                return myField;
            }
            
            set
            { 
                myField = value;
            }
        }
    }
  • = 할당 연산자를 통해 private 변수의 데이터를 저장 및 읽어오기가 가능하다.
  • get접근자만 구현할 경우 읽기 전용이 된다.

예제 프로그램

using System;

namespace Property
{
    class BirthdayInfo
    {
        private string name;
        private DateTime birthday;

        public string Name  // 읽기&쓰기
        {
            get
            {
                return name;
            }
            set 
            {
                name = value;
            }
        }

        public DateTime Birthday  // 읽기&쓰기
        {
            get
            {
                return birthday;
            }
            set
            {
                birthday = value;
            }
        }

        public int Age  // 읽기 전용
        {
            get
            {
                return new DateTime(DateTime.Now.Subtract(birthday).Ticks).Year;
            }
        }

    }

    class MainApp
    {
        static void Main(string[] args)
        {
            BirthdayInfo birth = new BirthdayInfo();
            // set : 쓰기
            birth.Name = "서현";
            birth.Birthday = new DateTime(1991, 6, 28);

            // get : 읽기
            Console.WriteLine("Name : {0}", birth.Name);
            Console.WriteLine("Birthday : {0}", birth.Birthday.ToShortDateString());
            Console.WriteLine("Age : {0}", birth.Age);
        }
    }
}


9.2 자동 구현 프로퍼티(Auto-Implemented Property)

  • 아래와 같이 필드를 단순히 읽고 쓰기만 하는 프로퍼티는 코드를 더 단순하게 만드는 자동 구현 프로퍼티를 사용할 수 있다. (C#3.0부터 도입)

    • 필드 선언 필요X

    • get접근자, set접근자 뒤에 세미콜론(;) 붙이기

      // 원래 코드 //
      public class NameCard
      {
         private string name;
         private string phoneNumber;
         
         public string Name
         {
             get { return name; }
             set { name = value; }
         }
         
         public string PhoneNumber
         {
             get { return phoneNumber; }
             set { phoneNumber = value; }
         }
      }
      
      // 자동 구현 프로퍼티 //
      public class NameCard
      {
      	// 1. 필드 생략
         
         public string Name
         {
             get; set;  // 2. get, set 선언 후 세미콜론 
         }
         
         public string PhoneNumber
         {
             get; set;
         }
      }
  • C#7.0부터는 자동 구현 프로퍼티 선언과 동시에 초기화를 수행할 수 있다.

    public class NameCard
    {
        public string Name{ get; set;} = "Unknown";
        public string PhoneNumber{ get; set; } = "000-0000";
    }

예제 프로그램

using System;

namespace AutoImplementedProperty
{
    class BirthdayInfo
    {
		// 선언 & 초기화
        public string Name { get; set;} = "Unknown";
        public DateTime Birthday{ get; set; } = new DateTime(1, 1, 1);
        // 읽기 전용
        public int Age
        {
            get
            {
                return new DateTime(DateTime.Now.Subtract(Birthday).Ticks).Year;
            }
        }
    }

    class MainApp
    {
        static void Main(string[] args)
        {
        	// 읽기
            BirthdayInfo birth = new BirthdayInfo();
            Console.WriteLine($"Name : {birth.Name}");
            Console.WriteLine($"Birthday : {birth.Birthday.ToShortDateString()}");
            Console.WriteLine($"Age : {birth.Age}");
			
            // 쓰기
            birth.Name = "서현";
            birth.Birthday = new DateTime(1991, 6, 28);
			
            // 읽기
            Console.WriteLine($"Name : {birth.Name}");
            Console.WriteLine($"Birthday : {birth.Birthday.ToShortDateString()}");
            Console.WriteLine($"Age : {birth.Age}");
        }
    }
}


9.3 프로퍼티와 생성자

  • 객체를 생성할 때 프로퍼티를 통해 각 필드를 초기화할 수 있다. (모든 프로퍼티가 아닌 초기화하고 싶은 프로퍼티만 넣으면 된다.)
    클래스이름 인스턴스 = new 클래스이름()
    	{
           프로퍼티1 =,
           프로퍼티2 =,
           프로퍼티3 =};

예제 프로그램

using System;

namespace ConstructorWithProperty
{
    class BirthdayInfo
    {
        public string Name
        {
            get;
            set;
        }

        public DateTime Birthday
        {
            get;
            set;
        }

        public int Age
        {
            get
            {
                return new DateTime(DateTime.Now.Subtract(Birthday).Ticks).Year;
            }
        }
    }

    class MainApp
    {
        static void Main(string[] args)
        {
        	// 객체 생성시 프로퍼티 이용해 필드 초기화
            BirthdayInfo birth = new BirthdayInfo()
            {
                Name = "서현",
                Birthday = new DateTime(1991, 6, 28)
            };

            Console.WriteLine("Name : {0}", birth.Name);
            Console.WriteLine("Birthday : {0}", birth.Birthday.ToShortDateString());
            Console.WriteLine("Age : {0}", birth.Age);
        }
    }
}


9.4 초기화 전용(Init-Only) 자동 구현 프로퍼티

  • C# 9.0부터 init접근자를 통해 객체 초기화를 할 때만 외부에서 프로퍼티 변경이 가능하도록 개선되었다.
    • set접근자 대신 init접근자를 명시하면 됨
      public class Transaction
      {
          public string From   {get; init;}
          public string To     {get; init;}
          public int    Amount {get; init;}
      }
  • 초기화 전용 자동 구현 프로퍼티를 사용해야 하는 데이터
    • 초기화가 한 차례 이루어진 후 변경되면 안되는 데이터(값이 한 번 주어지면 변경되면 안되는 데이터)
    • 성적표, 범죄 기록, 각종 국가 기록, 금융 거래 기록 등등

예제 프로그램

using System;

namespace System.Runtime.CompilerServices
{
    public class IsExternalInit{}
}

namespace InitOnly
{    
    class Transaction
    {
    	// set대신 init 선언
        public string From   { get; init; }
        public string To     { get; init; }
        public int    Amount { get; init; }
        
        public override string ToString()
        {
            return $"{From,-10} -> {To,-10} : ${Amount}";
        }
    }

    class MainApp
    {
        static void Main(string[] args)
        {
            Transaction tr1 = new Transaction{From="Alice", To="Bob",   Amount=100};
            Transaction tr2 = new Transaction{From="Bob", To="Charlie", Amount=50};
            Transaction tr3 = new Transaction{From="Charlie", To="Alice", Amount=50};
            // tr1.Amount = 30;  → 실행할 경우 컴파일 에러 발생 (init 접근자는 초기화 이후 발생하는 프로퍼티 수정을 허용하지 않으므로)
            Console.WriteLine(tr1);
            Console.WriteLine(tr2);
            Console.WriteLine(tr3);
        }
    }
}


9.5 레코드 형식으로 만드는 불변 객체

  • 불변 객체(Immutable Object) : 내부 상태(데이터)를 변경할 수 없는 객체
  • 레코드(Record) : 불변 객체에서 빈번히 이뤄지는 두 가지 연산(데이터 복사, 비교)을 편리하게 수행할 수 있도록 C# 9.0에서 도입된 형식
    • 레코드는 값 형식처럼 다룰 수 있는 불변 참조 형식

9.5.1 레코드 선언하기

  • record 키워드
  • 초기화 전용 자동 구현 프로퍼티 (init)

    레코드에는 초기화 전용 자동 구현 프로퍼티뿐만 아니라
    쓰기 가능한 프로퍼티와 필드도 자유롭게 선언할 수 있다.

    record RTransaction
    {
        public string From   { get; init; }
        public string To     { get; init; }
        public int    Amount { get; init; }
    }

예제 프로그램

using System;

namespace Record
{   
	// 레코드 선언
    record RTransaction
    {
        public string From   { get; init; }
        public string To     { get; init; }
        public int    Amount { get; init; }
        
        public override string ToString()
        {
            return $"{From,-10} -> {To,-10} : ${Amount}";
        }
    }    
    
    class MainApp
    {
        static void Main(string[] args)
        {
            RTransaction tr1 = new RTransaction
            {
                From="Alice", To="Bob", Amount=100
            };                        
            
            RTransaction tr2 = new RTransaction
            {
                From="Alice", To="Charlie", Amount=100
            };  

            Console.WriteLine(tr1);
            Console.WriteLine(tr2);
        }
    }
}


9.5.2 레코드 복사 : with

예제 프로그램

using System;

namespace System.Runtime.CompilerServices
{
    public class IsExternalInit{}
}

namespace WithExp
{
    record RTransaction
    {
        public string From   { get; init; }
        public string To     { get; init; }
        public int    Amount { get; init; }
        
        public override string ToString()
        {
            return $"{From,-10} -> {To,-10} : ${Amount}";
        }
    }
    
    class MainApp
    {
        static void Main(string[] args)
        {            
            RTransaction tr1 = new RTransaction{From="Alice", To="Bob",   Amount=100}; 
            // tr1 복사 - To 프로퍼티 값만 "Charlie"로 수정 
            RTransaction tr2 = tr1 with {To="Charlie"};
            // tr2 복사 - From, Amount 프로퍼티 각각 "Dave", 30으로 수정
            RTransaction tr3 = tr2 with {From="Dave", Amount=30};
            
            Console.WriteLine(tr1);
            Console.WriteLine(tr2);
            Console.WriteLine(tr3);
        }
    }
}


9.5.3 레코드 객체 비교하기

  • 컴파일러는 레코드의 상태를 비교하는 Equals() 메소드를 자동으로 구현해 Equals() 메소드를 구현하지 않아도 비교가 가능하다.

예제 프로그램

using System;

namespace RecordComp
{
    class CTransaction  // 클래스 - Equals() 구현 생략
    {
        public string From { get; init; }
        public string To { get; init; }
        public int Amount { get; init; }

        public override string ToString()
        {
            return $"{From,-10} -> {To,-10} : ${Amount}";
        }
    }

    record RTransaction  // 레코드
    {
        public string From { get; init; }
        public string To { get; init; }
        public int Amount { get; init; }

        public override string ToString()
        {
            return $"{From,-10} -> {To,-10} : ${Amount}";
        }
    }

    class MainApp
    {
        static void Main(string[] args)
        {
            CTransaction trA = new CTransaction { From = "Alice", To = "Bob", Amount = 100 };
            CTransaction trB = new CTransaction { From = "Alice", To = "Bob", Amount = 100 };

            Console.WriteLine(trA);
            Console.WriteLine(trB);
            Console.WriteLine($"trA equals to trB : {trA.Equals(trB)}");
            // Equals() 기본 구현은 내용 비교가 아닌 참조를 비교하므로 False 출력

            RTransaction tr1 = new RTransaction { From = "Alice", To = "Bob", Amount = 100 };
            RTransaction tr2 = new RTransaction { From = "Alice", To = "Bob", Amount = 100 };

            Console.WriteLine(tr1);
            Console.WriteLine(tr2);
            Console.WriteLine($"tr1 equals to tr2 : {tr1.Equals(tr2)}");
        }
    }
}

9.6 무명 형식(Anonymous Type)

  • 이름이 없는 형식
  • 형식 선언 & 인스턴스 할당 동시에 (보통 형식의 이름은 인스턴스를 만들기 위해 사용되는데 무명 형식은 이름이 없으므로 두개를 동시에 진행함)
  • 선언
  /* 중괄호 사이에 임의의 프로퍼티 이름을 적고 값을 할당하면
     그대로 새 형식의 프로퍼티가 된다. */
  var myInstance = new {Name="박상현", Age="17"};
  
  // 프로퍼티 사용
  Console.WriteLine(myInstance.Name, myInstance.Age);
  • 무명 형식의 프로퍼티는 처음 할당한 값의 변경이 불가하고 읽기만 할 수 있다.

예제 프로그램

using System;

namespace AnonymousType
{
    class MainApp
    {
        static void Main(string[] args)
        {
        	// 무명형식 : 선언과 동시에 인스턴스 할당
            var a = new { Name="박상현", Age=123 };
            Console.WriteLine("Name:{0}, Age:{1}", a.Name, a.Age);

            var b = new { Subject = "수학", Scores = new int[] { 90, 80, 70, 60 } };

            Console.Write("Subject:{0}, Scores: ", b.Subject);
            foreach(var score in b.Scores )
                Console.Write("{0} ", score );

            Console.WriteLine();
        }
    }
}


9.7 인터페이스의 프로퍼티

  • 선언
   interface 인터페이스이름
   {
       public  형식 프로퍼티이름1
       {
           get; set;
       }
       
       public 형식 프로퍼티이름2
       {
           get; set;
       }
       
       // ...
   }

예제 프로그램

using System;

namespace PropertiesInInterface
{
    interface INamedValue  // 인터페이스는 어떤 구현도 가지지 않는다.
    {
        string Name
        {
            get;
            set;
        }

        string Value
        {
            get;
            set;
        }
    }

    class NamedValue : INamedValue  // 파생 클래스 - 반드시 Name과 Value 구현해야함(자동구현프로퍼티 이용 가능)
    {
        public string Name
        {
            get;
            set;
        }

        public string Value
        {
            get;
            set;
        }
    }    

    class MainApp
    {
        static void Main(string[] args)
        {
            NamedValue name = new NamedValue() 
            { Name = "이름", Value = "박상현" };

            NamedValue height = new NamedValue() 
            { Name = "키", Value = "177Cm" };

            NamedValue weight = new NamedValue() 
            { Name = "몸무게", Value = "90Kg" };

            Console.WriteLine("{0} : {1}", name.Name, name.Value);
            Console.WriteLine("{0} : {1}", height.Name, height.Value);
            Console.WriteLine("{0} : {1}", weight.Name, weight.Value);
        }
    }
}


9.8 추상 클래스의 프로퍼티

  • abstract 한정자 사용
   abstract class 추상 클래스이름
   {
       abstract 데이터형식 프로퍼티이름
       {
           get;
           set;
       }
   }

예제 프로그램

using System;

namespace PropertiesInAbstractClass
{
	// 추상 클래스
    abstract class Product
    {
        private static int serial = 0;
        public string SerialID // 추상클래스는 구현을 가진 프로퍼티와
        {
            get { return String.Format("{0:d5}", serial++); }
        }

        abstract public DateTime ProductDate  // 구현이 없는 추상 프로퍼티 모두를 가질 수 있다.
        {
            get;
            set;
        }
    }
	
    // 파생 클래스
    class MyProduct : Product
    {	
    	// 파생 클래스는 기반 추상 클래스의 모든 추상 메소드뿐 아니라 추상 프로퍼티를 재정의해야 한다.
        public override DateTime ProductDate
        {
            get;
            set;
        }
    }

    class MainApp
    {
        static void Main(string[] args)
        {
            Product product_1 = new MyProduct() 
            { ProductDate = new DateTime(2010, 1, 10) };

            Console.WriteLine("Product:{0}, Product Date :{1}",
                product_1.SerialID, 
                product_1.ProductDate);

            Product product_2 = new MyProduct() 
            { ProductDate = new DateTime(2010, 2, 3) };
            
            Console.WriteLine("Product:{0}, Product Date :{1}",
                product_2.SerialID, 
                product_2.ProductDate);
            }
    }
}


연습 문제

  1. 다음 코드에서 NameCard 클래스의 GetAge(), SetAge(), GetName(), SetName() 메소드들을 프로퍼티로 변경해 작성하세요.
using System;

namespace Ex9_1
{
    class NameCard
    {
        private int age;
        private string name;
       
        public int GetAge()
        { return age; }
        
        public void SetAge(int value)
        { age = value; }
        
        public string GetName()
        { return name; }
        
        public void SetName(string value)
        { name = value; }
    }
    
    class MainApp
    {
        public static void Main()
        {
            NameCard MyCard = new NameCard();
            
            MyCard.SetAge(24);
            MyCard.SetName("상현");
            
            Console.WriteLine("나이 : {0}", MyCard.GetAge());
            Console.WriteLine("이름 : {0}", MyCard.GetName());
        }
    }
}
// 변경 후 코드
using System;

namespace Ex9_1
{
    class NameCard
    {
        public int Age
        {
            get;
            set;
        }
        public string Name
        {
            get;
            set;
        }
    }
    
    class MainApp
    {
        static void Main()
        {
            NameCard Mycard = new NameCard();

            Mycard.Age = 24;
            Mycard.Name = "상현";

            Console.WriteLine($"나이 : {Mycard.Age}");
            Console.WriteLine($"이름 : {Mycard.Name}");
        }


     }
}

  1. 다음 프로그램을 완성해서 다음과 같은 결과를 출력하도록 하세요. 단, 무명 형식을 이용해야 합니다.
이름:박상현, 나이:17
Real:3, Imaginary:-12
using System;

namespace Ex9_2
{
    class MainApp
    {
        static void Main(string[] args)
        {
            var nameCard = new { Name="박상현", Age=17};
            Console.WriteLine("이름:{0}, 나이:{1}", nameCard.Name, nameCard.Age);
            
            var complex = new { Real=3, Imaginary=-12};
            Console.WriteLine("Real:{0}, Imaginary:{1}",
                complex.Real, complex.Imaginary);
         }
     }
 }

0개의 댓글