[ 학습 흐름 ]
[ 학습 흐름 ]
- public 필드
- 프로퍼티
- 생성자
- 무명 형식
- 인터페이스의 프로퍼티
- 추상 클래스의 프로퍼티
[ public필드의 유혹 ]
[ public ]
class MyClass
{
private int myField;
public int GetMyField(){return myField;}
public void SetMyField(int MyValue){myField = newValue;}
}
MyClass obj = new MyClass();
obj.SetMyField(3);
Console.WriteLine(obj.GetMyField());
- 필드를 public으로 선언하고 , Get/Set 메서드 대신 = 연산자로 필드를 읽거나 할당하는 것은 편하지만 은닉성을 해친다 .
- Get/Set 메서드를 통해 은닉성을 지킬수 있지만, 편의성은 떨어진다 .
[ 메소드 보다 프로퍼티 ]
[ 프로퍼티 ]
class MyClass
{
private int myField;
public int MyField
{
get
{
return myField;
}
set
{
myField=value;
}
}
}
myClass obj = new MyClass();
obj.MyField=3;
Console.WriteLine(obj.MyField);
- get/set을 접근자라고 한다 .
- get 접근자는 필드로부터 값을 읽어온다
- set 접근자는 암묵적 매개변수인 value 키워드를 통해 필드에 값을 할당한다 .
- 프로퍼티를 구현하면 해당 클래스는 = 할당 연산자로 데이터를 저장하고 읽어올 수도 있다 .
[ 읽기 전용 프로퍼티 ]
class MyClass
{
get
{
return MyField;
}
}
- get메서드만 구현하면 해당 프로퍼티는 읽기 전용 프로퍼티가 된다 .
- 쓰기 전용 프로퍼티도 문법적으로는 가능하나 , 사용할 프로그래머에게 쓰기 전용 프로퍼티의 용도,동작 결과의
확인 방법을 알려줘야 한다 . 그렇지 않다면 해당 프로퍼티는 코드를 관리하기 어렵게 만든다 .
using System;
using System.IO;
using System.Security.Cryptography;
using System.Security.Cryptography.X509Certificates;
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();
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}");
}
}
[ 자동 구현 프로퍼티 ]
[ 자동 구현 프로퍼티 ]
public class NameCard
{
//자동 구현 프로퍼티
public string Name
{
get;set;
}
//자동 구현 프로퍼티를 선언과 동시에 초기화
public string Name{get;set;}="UnKnown"
}
- 프로퍼티는 데이터의 오염에 관해서는 메서드처럼 안전하고,데이터를 다룰때는 필드처럼 간결하다 .
- 단순히 읽고 씀에는 자동 구현 프로퍼티를 사용한다 .
- 필드를 생성 할 필요도 없다
- 자동 구현 프로퍼티에 초기값이 필요할때 생성자에 초기화 코드 작성할 필요없이 선언과 동시에 초기화가 가능하다 .
[ 프로퍼티와 생성자 ]
[ 생성자 ]
- 객체를 생성할 때 프로퍼티를 이용해 매개변수를 입력받아 각 필드를 초기화 할 수 있다 .
- 매개변수가 있는 생성자와 다르게 모든 프로퍼티를 초기화 할 필요 없다 .
using System;
using System.IO;
using System.Security.Cryptography;
using System.Security.Cryptography.X509Certificates;
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 : {birth.Name}");
Console.WriteLine($"BirthDay : {birth.BirthDay.ToShortDateString()}");
Console.WriteLine($"Age : {birth.Age}");
}
}
[ 초기화 전용 자동 구현 프로퍼티 ]
[ 초기화 전용 자동 구현 프로퍼티 ]
class Transaction
{
public Transaction(string from,string to,int amount)
{
this.from=from,this.to=to,this.amount=amount;
}
string from;
string to;
int amount;
public string From{get{return from;}}
public string To{get{return to;}}
public int Amount{get{return amount;}}
}
- 의도치 않은 데이터오염을 방지하는 장치는 접근 한정자,readonly 필드,readonly 구조체, 튜플등이 있다 .
- 생성자를 통해 필드를 초기화 , 그 필드에 접근하는 프로퍼티는 get 접근자만 가지도록 했다 . (읽기전용)
public class Transaction
{
public string From(get;init;)
public string To(get;init;)
public int Amount(get;init;)
}
- init 접근자를 통해 외부에서 프로퍼티를 변경 가능하지만 , 객체를 초기화 하는 순간에만 가능하게 한다 .
- 초기화 된 이후로 변경 할 수 없고 , 필드에 get 만 가능하다 .
- 객체 초기화가 된 이후 초기화 전용 자동구현 프로퍼티를 수정하려 한다면 컴파일 에러가 발생한다 .
[ 프로퍼티 초기화를 강제하는 required ]
[ required ]
- 수식하는 프로퍼티를 객체가 초기화 될 때 반드시 초기화 되도록 컴파일 수준에서 강제한다 .
using System;
namespace RequiredProperty;
class BirthdayInfo
{
public required string Name{get;set;}
public required DateTime BirthDay { get; init; }
public int Age
{
get{return new DateTime(DateTime.Now.Subtract(BirthDay).Ticks).Year;}
}
}
class MainApp
{
static void Main(string[] args)
{
BirthdayInfo birth = new BirthdayInfo()
{
//required 한정했기에 초기화가 없다면 컴파일 에러
Name = "서현",
BirthDay = new DateTime(1991,6,28)
};
Console.WriteLine($"Name : {birth.Name}");
Console.WriteLine($"BirthDay : {birth.BirthDay.ToShortDateString()}");
Console.WriteLine($"Age : {birth.Age}");
}
}
[ 레코드 형식으로 만드는 불변 객체 ]
[ 불변 객체 ]
불변객체의 정의
- 내부 상태 (데이터)를 변경할 수 없는 객체
- 위 특성때문에 데이터 복사와 비교가 많이 이뤄진다 .
- 불변객체는 참조 형식은 모든 필드를 readonly로 / 값 형식은 readonly struct로 구조체를 선언한다 .
값 형식의 복사
- 값 형식 객체는 다른 객체 할당시 깊은 복사 수행 .
- 필드가 많을수록 , 여러곳에서 사용해야 할수록 복사비용은 커짐
참조 형식의 복사
- 참조 형식은 메모리 주소만 복사하기에 이런 오버헤드가 없음
- 그러나 직접 깊은 복사의 구현이 필요함
값 형식의 비교
- 모든 필드를 1:1 비교
참조 형식의 비교
- 비교시 직접 비교 코드 작성
- 보통 object로 부터 상속하는 Equals()메소드를 오버라이딩
참조 형식의 불변객체의 장점
- 함수 호출 인수, 컬렉션 요소로 사용시 복사비용을 줄일 수 있다
값 형식의 불변객체의 장점
- 새 상태 표현과 상태 확인을 위해 깊은 복사와 내용비교가 필수이기에 편리하다
[ 레코드 ]
레코드 형식
- 레코드는 불변 객체에서 복사와 비교를 편리하게 수행하기 위해 도입된 형식
- 값 형식처럼 다룰 수 있는 불변 참조 형식 . 참조 형식의 비용 효율과 값 형식이 주는 편리함 제공
레코드 선언하기
record RTransaction
{
public string From{get;init;}
public string To{get;init;}
public int Amount{get;init;}
}
//레코드로 인스턴스를 만들면 불변 객체가 생성
RTransaction tr1 = new RTransactioin{From="Alice",To="Bob",Amount=100};
- 레코드는 record 키워드와 초기화 전용 자동 구현 프로퍼티를 함께 이용하여 선언
- 쓰기 가능한 프로퍼티,필드도 자유롭게 선언해 넣을 수 있다 .
using System;
namespace Record;
record RTransaction
{
public string From { init; get; }
public string To { init; get; }
public int Amount { init; get; }
public override string ToString()
{
return $"{From,-10}->{To,-10}:${Amount}";
}
}
class MainApp
{
static void Main(string[] args)
{
RTransaction tr1=new RTransaction { From="Aclice",To="Bob",Amount=100};
RTransaction tr2 = new RTransaction { From = "Aclice", To = "Charlie", Amount = 100 };
Console.WriteLine(tr1);
Console.WriteLine(tr2);
}
}
[ with를 이용한 레코드 복사 ]
- C# 컴파일러는 레코드 형식을 위한 복사 생성자를 자동으로 작성한다
- 이는 protected 로 선언되기 때문에 명시적 호출은 어렵고 , with 식을 이용해야 한다 .
- with 식은 tr1을 복사 , To 프로퍼티 값만 수정해서 tr라는 새로운 레코드 객체를 생성한다 .
- with 식은 객체 상태(프로퍼티)가 다양할수록 유용하다 .
using System;
namespace Record;
record RTransaction
{
public string From { init; get; }
public string To { init; get; }
public int Amount { init; get; }
public override string ToString()
{
return $"{From,-10}->{To,-10}:${Amount}";
}
}
class MainApp
{
static void Main(string[] args)
{
RTransaction tr1=new RTransaction { From="Aclice",To="Bob",Amount=100};
RTransaction tr2 = tr1 with { To = "Charlie" };
RTransaction tr3 = tr2 with { From = "Dave", Amount = 30 };
Console.WriteLine(tr1);
Console.WriteLine(tr2);
Console.WriteLine(tr3);
}
}
[ 레코드 객체 비교하기 ]
- C# 컴파일러는 레코드의 상태를 비교하는 Equals()메소드를 자동으로 구현한다 .
- 클래스 객체는 Equals 메서드 오버라이딩을 통해 직접 구현해야 한다 .
- 레코드는 참조 형식이지만 값 형식처럼 Equals()메소드를 구현하지 않아도 비교가 가능하다 .
using System;
namespace RecordComp;
record RTransaction
{
public string From { init; get; }
public string To { init; get; }
public int Amount { init; get; }
public override string ToString()
{
return $"{From,-10}->{To,-10}:${Amount}";
}
}
class CTransaction
{
public string From { init; get; }
public string To { init; get; }
public int Amount { init; get; }
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);
//Equals()의 기본구현은 내용비교가 아닌 참조비교이기에 false
Console.WriteLine($"trA equals to trB : {trA.Equals(trB)}");
RTransaction tr1 =new RTransaction { From="Aclice",To="Bob",Amount=100};
RTransaction tr2 = new RTransaction { From = "Aclice", To = "Bob", Amount = 100 };
Console.WriteLine(tr1);
Console.WriteLine(tr2);
Console.WriteLine($"tr1 equals to tr2 : {tr1.Equals(tr2)}");
}
}
[ 무명 형식 ]
[ 무명 형식 ]
- 형식의 선언과 동시에 인스턴스를 할당한다 .
- 인스턴스를 만들고 다시는 사용하지 않을때 요긴하다 .
- 무명 형식의 프로퍼티에 할당된 값을 변경이 불가하다 .
using System;
namespace AnonymousType;
class MainApp
{
static void Main(string[] args)
{
var a = new { Name = "박상현", Age = 123 };
Console.WriteLine($"Name{a.Name} , Age{a.Age}");
var b = new { Subject = "수학", Scores = new int[] { 90, 80, 70, 60 } };
Console.WriteLine($"Subject: {b.Subject} Scores:");
foreach (var score in b.Scores)
Console.Write($"{score} ");
Console.WriteLine();
}
}
[ 인터페이스의 프로퍼티 ]
[ 인터페이스의 프로퍼티 ]
- 메소드뿐 아니라 프로퍼티 , 인덱서도 가질 수 있다 .
- 인터페이스에 들어가는 프로퍼티는 구현을 가지지 않고 , 클래스의 자동구현프로퍼티와 모습이 동일하다
- 파생 클래스는 프로퍼티 구현시 자동 구현 프로퍼티를 이용 할 수도 있다.
using System;
namespace AnonymousType;
class MainApp
{
interface INameValue
{
string Name
{
get;
set;
}
string Value
{
get;
set;
}
}
class NameValue:INameValue
{
public string Name { get; set; }
public string Value { get ; set ; }
}
static void Main(string[] args)
{
NameValue name = new NameValue() { Name = "이름", Value = "박상현" };
Console.WriteLine("{name.Name} : {name.Value}");
}
}
[ 추상 클래스의 프로퍼티 ]
[ 추상 클래스의 프로퍼티 ]
abstract class 추상 클래스 이름
{
abstract 데이터형식 프로퍼티이름
{
get;set;
}
}
- 클래스처럼 구현된 프로퍼티를 가질 수 있는 한편, 인터페이스처럼 구현되지 않은 프로퍼티도 가질 수 있다 .
- 이것을 추상 프로퍼티라고 한다 .
using System;
namespace PropertiesInAbstractClass;
abstract class Product
{
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 product1 = new MyProduct() { ProductDate=new DateTime(2023,1,10)};
Console.WriteLine("Product: {0}, ProductDate : {1}",product1.SerialID,product1.ProductDate);
Product product2 = new MyProduct() { ProductDate = new DateTime(2023, 4, 10) };
Console.WriteLine("Product: {0}, ProductDate : {1}", product2.SerialID, product2.ProductDate);
}
}
'C# > 이것이 C#이다' 카테고리의 다른 글
[ 이것이 C#이다 ] Chapter 11 . 일반화 프로그래밍 (0) | 2024.03.18 |
---|---|
[ 이것이 C#이다 ] Chapter 10 . 배열과 컬렉션 그리고 인덱서 (0) | 2024.03.14 |
[ 이것이 C#이다 ] Chapter 08 . 인터페이스와 추상 클래스 (2) | 2024.03.12 |
[ 이것이 C#이다 ] Chapter 07 . 클래스 (0) | 2024.03.06 |
[ 이것이 C#이다 ] Chapter 06 . 메소드로 코드 간추리기 (0) | 2024.03.04 |