C# 12에는 매개 변수를 형식 본문의 어디에서나 사용할 수 있는 생성자를 선언하는 간결한 구문을 제공하는 기본 생성자가 도입되었습니다.
이 문서에서는 형식에서 기본 생성자를 선언하고 기본 생성자 매개 변수를 저장할 위치를 인식하는 방법을 설명합니다. 다른 생성자에서 기본 생성자를 호출하고 형식의 멤버에서 기본 생성자 매개 변수를 사용할 수 있습니다.
필수 조건
- 최신 .NET SDK
- Visual Studio Code 편집기
- C# 개발 키트
기본 생성자에 대한 규칙 이해
struct
또는 class
선언에 매개 변수를 추가하여 기본 생성자만들 수 있습니다. 기본 생성자 매개 변수는 클래스 정의 전체의 범위에 있습니다. 기본 생성자 매개 변수를 클래스 정의 전체의 범위에 있더라도 매개 변수로 간주하는 것이 중요합니다.
몇 가지 규칙은 이러한 생성자가 매개 변수임을 명확히 합니다.
- 기본 생성자 매개 변수는 필요하지 않은 경우 저장되지 않을 수 있습니다.
- 기본 생성자 매개 변수는 클래스의 멤버가 아닙니다. 예를 들어, 기본 생성자 매개 변수
param
은this.param
으로 액세스할 수 없습니다. - 기본 생성자 매개 변수를 할당할 수 있습니다.
- 레코드 형식을 제외하고 기본 생성자 매개 변수는 속성이 되지 않습니다.
이러한 규칙은 다른 생성자 선언을 포함하여 모든 메서드에 대한 매개 변수에 대해 이미 정의된 것과 동일한 규칙입니다.
기본 생성자 매개 변수에 대한 가장 일반적인 용도는 다음과 같습니다.
- 생성자 호출에
base()
인수로 전달 - 멤버 필드 또는 속성 초기화
- 인스턴스 멤버에서 생성자 매개 변수 참조
클래스 this()
있어야 합니다. 이 규칙은 기본 생성자 매개 변수가 형식 본문의 모든 위치에 할당되도록 합니다.
변경할 수 없는 속성 또는 필드 초기화
다음 코드는 기본 생성자 매개 변수에서 계산되는 두 개의 읽기 전용(변경할 수 없는) 속성을 초기화합니다.
public readonly struct Distance(double dx, double dy)
{
public readonly double Magnitude { get; } = Math.Sqrt(dx * dx + dy * dy);
public readonly double Direction { get; } = Math.Atan2(dy, dx);
}
이 예제에서는 기본 생성자를 사용하여 계산된 읽기 전용 속성을 초기화합니다.
Magnitude
및 Direction
속성의 필드 이니셜라이저는 기본 생성자 매개 변수를 사용합니다. 주 생성자 매개 변수는 구조체의 다른 곳에서는 사용되지 않습니다. 코드는 다음과 같은 방식으로 작성된 것처럼 구조체를 만듭니다.
public readonly struct Distance
{
public readonly double Magnitude { get; }
public readonly double Direction { get; }
public Distance(double dx, double dy)
{
Magnitude = Math.Sqrt(dx * dx + dy * dy);
Direction = Math.Atan2(dy, dx);
}
}
이 기능을 사용하면 필드 또는 속성을 초기화하는 데 인수가 필요할 때 필드 이니셜라이저를 더 쉽게 사용할 수 있습니다.
변경 가능한 상태 만들기
이전 예제에서는 기본 생성자 매개 변수를 사용하여 읽기 전용 속성을 초기화합니다. 읽기 전용이 아닌 속성에 기본 생성자를 사용할 수도 있습니다.
다음 코드를 고려합니다.
public struct Distance(double dx, double dy)
{
public readonly double Magnitude => Math.Sqrt(dx * dx + dy * dy);
public readonly double Direction => Math.Atan2(dy, dx);
public void Translate(double deltaX, double deltaY)
{
dx += deltaX;
dy += deltaY;
}
public Distance() : this(0,0) { }
}
이 예제에서 Translate
메서드는 dx
및 dy
구성 요소를 변경하며, Magnitude
및 Direction
속성은 액세스 시 계산되어야 합니다. 보다 크거나 같음(=>
) 연산자는 식 본문 get
접근자를 지정하는 반면, 같음(=
) 연산자는 초기값을 지정합니다.
이 버전의 코드는 매개 변수가 없는 생성자를 구조체에 추가합니다. 매개 변수가 없는 생성자는 모든 기본 생성자 매개 변수가 초기화되도록 기본 생성자를 호출해야 합니다. 기본 생성자 속성은 메서드에서 액세스되고 컴파일러는 각 매개 변수를 나타내는 숨겨진 필드를 만듭니다.
다음 코드는 컴파일러가 생성하는 항목의 근사치를 보여 줍니다. 실제 필드 이름은 유효한 CIL(공용 중간 언어) 식별자이지만 유효한 C# 식별자는 아닙니다.
public struct Distance
{
private double __unspeakable_dx;
private double __unspeakable_dy;
public readonly double Magnitude => Math.Sqrt(__unspeakable_dx * __unspeakable_dx + __unspeakable_dy * __unspeakable_dy);
public readonly double Direction => Math.Atan2(__unspeakable_dy, __unspeakable_dx);
public void Translate(double deltaX, double deltaY)
{
__unspeakable_dx += deltaX;
__unspeakable_dy += deltaY;
}
public Distance(double dx, double dy)
{
__unspeakable_dx = dx;
__unspeakable_dy = dy;
}
public Distance() : this(0, 0) { }
}
컴파일러에서 만든 스토리지
이 섹션의 첫 번째 예제에서는 컴파일러가 기본 생성자 매개 변수의 값을 저장할 필드를 만들 필요가 없었습니다. 그러나 두 번째 예제에서는 기본 생성자 매개 변수가 메서드 내에서 사용되므로 컴파일러는 매개 변수에 대한 스토리지를 만들어야 합니다.
컴파일러는 매개 변수가 형식의 멤버 본문에 액세스하는 경우에만 모든 기본 생성자에 대한 스토리지를 만듭니다. 그렇지 않으면 기본 생성자 매개 변수가 개체에 저장되지 않습니다.
종속성 주입 사용
기본 생성자에 대한 또 다른 일반적인 용도는 종속성 주입에 대한 매개 변수를 지정하는 것입니다. 다음 코드는 사용하기 위해 서비스 인터페이스가 필요한 간단한 컨트롤러를 만듭니다.
public interface IService
{
Distance GetDistance();
}
public class ExampleController(IService service) : ControllerBase
{
[HttpGet]
public ActionResult<Distance> Get()
{
return service.GetDistance();
}
}
기본 생성자는 클래스에 필요한 매개 변수를 명확하게 나타냅니다. 클래스의 다른 변수와 마찬가지로 기본 생성자 매개 변수를 사용합니다.
기본 클래스 초기화
파생 클래스의 기본 생성자에서 기본 클래스에 대한 기본 생성자를 호출할 수 있습니다. 이 방법은 기본 클래스에서 기본 생성자를 호출해야 하는 파생 클래스를 작성하는 가장 쉬운 방법입니다. 다른 계좌 유형을 은행으로 나타내는 클래스의 계층 구조를 고려합니다. 다음 코드는 기본 클래스의 모양을 보여줍니다.
public class BankAccount(string accountID, string owner)
{
public string AccountID { get; } = accountID;
public string Owner { get; } = owner;
public override string ToString() => $"Account ID: {AccountID}, Owner: {Owner}";
}
유형에 관계없이 모든 은행 계좌에는 계정 번호 및 소유자에 대한 속성이 있습니다. 완료된 애플리케이션에서 기본 클래스에 다른 일반적인 기능을 추가할 수 있습니다.
많은 형식에는 생성자 매개 변수에 대한 보다 구체적인 유효성 검사가 필요합니다. 예를 들어 BankAccount
클래스에는 owner
및 accountID
매개 변수에 대한 특정 요구 사항이 있습니다. 매개변수 owner
는 비어 있거나 공백이 아니어야 하며, 매개변수 accountID
는 10자리 숫자를 포함하는 문자열이어야 합니다. 해당 속성을 할당할 때 이 유효성 검사를 추가할 수 있습니다.
public class BankAccount(string accountID, string owner)
{
public string AccountID { get; } = ValidAccountNumber(accountID)
? accountID
: throw new ArgumentException("Invalid account number", nameof(accountID));
public string Owner { get; } = string.IsNullOrWhiteSpace(owner)
? throw new ArgumentException("Owner name cannot be empty", nameof(owner))
: owner;
public override string ToString() => $"Account ID: {AccountID}, Owner: {Owner}";
public static bool ValidAccountNumber(string accountID) =>
accountID?.Length == 10 && accountID.All(c => char.IsDigit(c));
}
이 예제에서는 속성에 할당하기 전에 생성자 매개 변수의 유효성을 검사하는 방법을 보여줍니다. 같은 String.IsNullOrWhiteSpace(String) 기본 제공 메서드나 고유한 유효성 검사 메서드(예: ValidAccountNumber
.)를 사용할 수 있습니다. 이 예제에서는 생성자가 이니셜라이저를 호출할 때 어떤 예외라도 throw됩니다. 생성자 매개 변수를 사용하여 필드를 할당하지 않으면 생성자 매개 변수에 처음으로 액세스할 때 예외가 발생합니다.
하나의 파생 클래스는 검사 계정을 나타낼 수 있습니다.
public class CheckingAccount(string accountID, string owner, decimal overdraftLimit = 0) : BankAccount(accountID, owner)
{
public decimal CurrentBalance { get; private set; } = 0;
public void Deposit(decimal amount)
{
if (amount < 0)
{
throw new ArgumentOutOfRangeException(nameof(amount), "Deposit amount must be positive");
}
CurrentBalance += amount;
}
public void Withdrawal(decimal amount)
{
if (amount < 0)
{
throw new ArgumentOutOfRangeException(nameof(amount), "Withdrawal amount must be positive");
}
if (CurrentBalance - amount < -overdraftLimit)
{
throw new InvalidOperationException("Insufficient funds for withdrawal");
}
CurrentBalance -= amount;
}
public override string ToString() => $"Account ID: {AccountID}, Owner: {Owner}, Balance: {CurrentBalance}";
}
파생된 CheckingAccount
클래스에는 기본 클래스에 필요한 모든 매개 변수를 사용하는 기본 생성자와 기본값이 있는 다른 매개 변수가 있습니다. 주 생성자는 : BankAccount(accountID, owner)
구문을 사용하여 기본 생성자를 호출합니다. 이 식은 기본 클래스의 형식과 기본 생성자의 인수를 모두 지정합니다.
파생 클래스는 기본 생성자를 사용할 필요가 없습니다. 다음 예제와 같이 기본 클래스에 대한 기본 생성자를 호출하는 파생 클래스에서 생성자를 만들 수 있습니다.
public class LineOfCreditAccount : BankAccount
{
private readonly decimal _creditLimit;
public LineOfCreditAccount(string accountID, string owner, decimal creditLimit) : base(accountID, owner)
{
_creditLimit = creditLimit;
}
public decimal CurrentBalance { get; private set; } = 0;
public void Deposit(decimal amount)
{
if (amount < 0)
{
throw new ArgumentOutOfRangeException(nameof(amount), "Deposit amount must be positive");
}
CurrentBalance += amount;
}
public void Withdrawal(decimal amount)
{
if (amount < 0)
{
throw new ArgumentOutOfRangeException(nameof(amount), "Withdrawal amount must be positive");
}
if (CurrentBalance - amount < -_creditLimit)
{
throw new InvalidOperationException("Insufficient funds for withdrawal");
}
CurrentBalance -= amount;
}
public override string ToString() => $"{base.ToString()}, Balance: {CurrentBalance}";
}
클래스 계층 구조 및 기본 생성자에 대한 한 가지 잠재적인 문제가 있습니다. 매개 변수는 파생 클래스와 기본 클래스 모두에서 사용되므로 기본 생성자 매개 변수의 여러 복사본을 만들 수 있습니다. 다음 코드는 owner
및 accountID
매개 변수 각각에 대해 두 개의 복사본을 만듭니다.
public class SavingsAccount(string accountID, string owner, decimal interestRate) : BankAccount(accountID, owner)
{
public SavingsAccount() : this("default", "default", 0.01m) { }
public decimal CurrentBalance { get; private set; } = 0;
public void Deposit(decimal amount)
{
if (amount < 0)
{
throw new ArgumentOutOfRangeException(nameof(amount), "Deposit amount must be positive");
}
CurrentBalance += amount;
}
public void Withdrawal(decimal amount)
{
if (amount < 0)
{
throw new ArgumentOutOfRangeException(nameof(amount), "Withdrawal amount must be positive");
}
if (CurrentBalance - amount < 0)
{
throw new InvalidOperationException("Insufficient funds for withdrawal");
}
CurrentBalance -= amount;
}
public void ApplyInterest()
{
CurrentBalance *= 1 + interestRate;
}
public override string ToString() => $"Account ID: {accountID}, Owner: {owner}, Balance: {CurrentBalance}";
}
이 예제에서 강조 표시된 줄은 메서드가 기본 클래스 속성(owner
및)이 아닌 기본 생성자 매개 변수(Owner
및accountID
AccountID
)를 사용한다는 것을 ToString
보여 줍니다. 그 결과 파생 클래스 SavingsAccount
는 매개 변수 복사본에 대한 스토리지를 만듭니다. 파생 클래스의 복사본은 기본 클래스의 속성과 다릅니다. 기본 클래스 속성을 수정할 수 있는 경우 파생 클래스의 인스턴스에 수정이 표시되지 않습니다. 컴파일러는 파생 클래스에서 사용되고 기본 클래스 생성자에 전달되는 기본 생성자 매개 변수에 대한 경고를 실행합니다. 이 경우 수정은 기본 클래스의 속성을 사용하는 것입니다.
관련 콘텐츠
.NET