다음을 통해 공유


필수 멤버

메모

이 문서는 기능 사양입니다. 사양은 기능의 디자인 문서 역할을 합니다. 여기에는 기능 디자인 및 개발 중에 필요한 정보와 함께 제안된 사양 변경 내용이 포함됩니다. 이러한 문서는 제안된 사양 변경이 완료되고 현재 ECMA 사양에 통합될 때까지 게시됩니다.

기능 사양과 완료된 구현 간에 약간의 불일치가 있을 수 있습니다. 이러한 차이는관련 LDM(언어 디자인 모임) 노트에서 캡처됩니다.

사양문서에서 기능 스펙릿을 C# 언어 표준으로 채택하는 과정에 대해 더 자세히 알아볼 수 있습니다.

요약

이 제안은 개체를 초기화하는 동안 속성 또는 필드를 설정해야 하므로 인스턴스 작성자가 생성 사이트에서 개체 이니셜라이저의 멤버에 대한 초기 값을 제공하도록 하는 방법을 추가합니다.

동기

오늘날 개체 계층 구조에는 계층 구조의 모든 수준에서 데이터를 전달하기 위해 많은 상용구가 필요합니다. C# 8에 정의된 것처럼 Person 관련된 간단한 계층 구조를 살펴보겠습니다.

class Person
{
    public string FirstName { get; }
    public string MiddleName { get; }
    public string LastName { get; }

    public Person(string firstName, string lastName, string? middleName = null)
    {
        FirstName = firstName;
        LastName = lastName;
        MiddleName = middleName ?? string.Empty;
    }
}

class Student : Person
{
    public int ID { get; }
    public Student(int id, string firstName, string lastName, string? middleName = null)
        : base(firstName, lastName, middleName)
    {
        ID = id;
    }
}

여기에는 많은 반복이 진행됩니다.

  1. 계층의 뿌리에서, 각 속성의 형식을 두 번 반복해야 했고, 이름은 네 번 반복해야 했습니다.
  2. 파생 수준에서 상속된 각 속성의 형식을 한 번 반복해야 했으며 이름은 두 번 반복해야 했습니다.

이는 3개의 속성과 1개 수준의 상속이 있는 간단한 계층 구조이지만, 이러한 유형의 계층 구조에 대한 많은 실제 예제는 여러 수준 더 깊게 진행되어 더 크고 더 많은 수의 속성을 누적하여 전달합니다. Roslyn은 예를 들어 CST 및 AST를 만드는 다양한 트리 형식에서 이러한 코드베이스 중 하나입니다. 이 코드 구조 중첩은 타입의 생성자와 정의를 자동으로 생성하기 위한 코드 생성기가 있을 정도로 번거로울 정도로 복잡하며, 많은 고객들도 이 문제를 비슷한 방식으로 해결하고 있습니다. C# 9에는 레코드가 도입되어 일부 시나리오에서 이를 향상할 수 있습니다.

record Person(string FirstName, string LastName, string MiddleName = "");
record Student(int ID, string FirstName, string LastName, string MiddleName = "") : Person(FirstName, LastName, MiddleName);

record중복의 첫 번째 원본은 제거되지만, 두 번째 중복 원본은 그대로 남아 있습니다. 불행히도 이는 계층 구조가 확장됨에 따라 증가하는 중복의 원인이며, 계층 구조를 변경한 후 모든 위치를 추적해야 하는, 수정하기 가장 어려운 부분입니다. 이는 프로젝트 전반에 걸쳐 잠재적으로 소비자에게 영향을 미칠 수 있습니다.

이러한 중복을 방지하기 위한 해결 방법으로, 우리는 오랫동안 소비자들이 생성자 작성을 방지하는 방법으로 개체 이니셜라이저를 수용하는 것을 보았습니다. 그러나 C# 9 이전에는 다음과 같은 2가지 주요 단점이 있었습니다.

  1. 개체 계층 구조는 모든 속성에 set 접근자를 사용하여 완전히 수정 가능해야 합니다.
  2. 그래프에서 개체의 모든 인스턴스화가 모든 멤버를 설정하도록 할 수 있는 방법은 없습니다.

C# 9에서는 init 접근자를 도입하여 여기서 첫 번째 문제를 다시 해결했습니다. 이 기능을 사용하면 이러한 속성을 개체 만들기/초기화에 설정할 수 있지만 이후에는 설정할 수 없습니다. 그러나 우리는 여전히 두 번째 문제를 안고 있습니다. C#의 속성은 C# 1.0 이후로 선택 사항이었습니다. C# 8.0에서 도입된 Nullable 참조 형식은 이 문제의 일부를 해결했습니다. 생성자가 nullable이 아닌 참조 형식 속성을 초기화하지 않으면 사용자에게 경고가 표시됩니다. 그러나 이렇게 하면 문제가 해결되지 않습니다. 여기서 사용자는 생성자에서 형식의 큰 부분을 반복하지 않고 요구 사항 전달하여 소비자에게 속성을 설정하려고 합니다. 값 형식이므로 ID에서 Student에 대한 경고를 제공하지 않습니다. 이러한 시나리오는 EF Core와 같은 데이터베이스 모델 ORM에서 매우 일반적입니다. 이 경우 공용 매개 변수 없는 생성자가 있어야 하지만 속성의 null 허용 여부에 따라 행의 null 허용 여부를 구동해야 합니다.

이 제안은 C#: 필수 멤버에 새로운 기능을 도입하여 이러한 문제를 해결하려고 합니다. 필요한 멤버는 여러 생성자 및 기타 시나리오에 대한 유연성을 허용하도록 다양한 사용자 지정을 사용하여 형식 작성자가 아닌 소비자가 초기화해야 합니다.

상세 디자인

class, structrecord 형식은 required_member_list을 선언할 수 있는 능력을 갖추게 됩니다. 이 목록은 필요한간주되는 형식의 모든 속성 및 필드 목록이며 형식 인스턴스를 생성하고 초기화하는 동안 초기화해야 합니다. 형식은 기본 형식에서 이러한 목록을 자동으로 상속하여 상용구 및 반복 코드를 제거하는 원활한 환경을 제공합니다.

required 한정자

'required'field_modifierproperty_modifier한정자 목록에 추가합니다. 형식의 required_member_listrequired가 적용된 모든 멤버로 구성됩니다. 따라서 이전의 Person 형식은 다음과 같습니다.

public class Person
{
    // The default constructor requires that FirstName and LastName be set at construction time
    public required string FirstName { get; init; }
    public string MiddleName { get; init; } = "";
    public required string LastName { get; init; }
}

형식에 required_member_list가 있는 경우, 해당 형식의 모든 생성자는 소비자가 목록의 모든 속성을 초기화해야 하는 계약을(를) 자동으로 알립니다. 생성자가 적어도 생성자 자체만큼 액세스할 수 없는 멤버가 필요한 계약을 보급하는 것은 오류입니다. 예를 들어:

public class C
{
    public required int Prop { get; protected init; }

    // Advertises that Prop is required. This is fine, because the constructor is just as accessible as the property initer.
    protected C() {}

    // Error: ctor C(object) is more accessible than required property Prop.init.
    public C(object otherArg) {}
}

required class, structrecord 형식에서만 유효합니다. interface 형식에서는 유효하지 않습니다. required는 다음 수식어와 결합할 수 없습니다.

  • fixed
  • ref readonly
  • ref
  • const
  • static

required 인덱서에 적용할 수 없습니다.

컴파일러는 Obsolete이 형식의 필수 멤버에 적용될 때 다음과 같은 경고를 발생시키며:

  1. 형식이 Obsolete으로 표시되지 않았거나
  2. SetsRequiredMembersAttribute으로 지정되지 않은 생성자는 Obsolete으로 표시되지 않습니다.

SetsRequiredMembersAttribute

필수 멤버가 있거나 기본 형식이 필수 멤버를 지정하는 형식의 모든 생성자는 해당 생성자를 호출할 때 소비자가 해당 멤버를 설정해야 합니다. 이 요구 사항에서 생성자를 제외하기 위해 생성자는 이러한 요구 사항을 제거하는 SetsRequiredMembersAttribute특성을 지정할 수 있습니다. 생성자 본문은 형식의 필수 멤버를 확실히 설정하도록 유효성을 검사하지 않습니다.

SetsRequiredMembersAttribute 생성자에서 모든 요구 사항을 제거하고 이러한 요구 사항은 어떤 방식으로든 유효성을 검사하지 않습니다. 참고: 잘못된 필수 멤버 목록이 있는 형식에서 상속이 필요할 경우, 이를 해결하는 방법은 있습니다. 해당 형식의 생성자에 SetsRequiredMembersAttribute을 표시하면 오류가 보고되지 않습니다.

C 생성자가 base특성이 지정된 this 또는 SetsRequiredMembersAttribute 생성자에 연결되는 경우, CSetsRequiredMembersAttribute특성이 지정되어야 합니다.

레코드 형식의 경우, 레코드 형식이나 해당 기본 형식에 필수 멤버가 있는 경우 합성된 복사 생성자의 SetsRequiredMembersAttribute을 내보냅니다.

NB: 이 제안의 이전 버전에는 초기화와 관련된 더 큰 metalanguage가 있어 생성자에서 개별 필수 멤버를 추가 및 제거할 수 있으며 생성자가 모든 필수 멤버를 설정하고 있는지 유효성을 검사할 수 있습니다. 초기 릴리스에서는 너무 복잡한 것으로 간주되어 제거되었습니다. 더 복잡한 계약 및 수정 사항을 이후 기능으로 추가하는 것을 볼 수 있습니다.

집행

필요한 멤버 Ci를 가진 T 형식의 모든 생성자 R에 대해 Ci를 호출하는 소비자는 다음 중 하나를 수행해야 합니다.

  • R 모든 멤버를 object_creation_expression에서 object_initializer 안에 설정합니다.
  • 또는 R 모든 멤버를 named_argument_list 섹션을 통해 attribute_target에서 설정할 수 있습니다.

CiSetsRequiredMembers과 연결되지 않는 한.

현재 컨텍스트가 object_initializer을 허용하지 않거나 attribute_target이 아니며, 또한 CiSetsRequiredMembers특성으로 지정되지 않은 경우 Ci을 호출하는 것은 오류입니다.

new() 제약 조건

계약을 광고하는 매개변수가 없는 생성자를 가진 형식은 new()로 제한된 형식 매개변수로 대체될 수 없습니다. 이는 제네릭 인스턴스화가 요구 사항이 충족되는지를 확인할 방법이 없기 때문입니다.

struct defaults

필수 멤버는 struct 또는 default사용하여 만든 default(StructType) 형식의 인스턴스에 적용되지 않습니다. struct 인스턴스가 new StructType()로 생성된 경우에는, 매개 변수가 없는 생성자가 없어 기본 구조체 생성자가 사용되는 StructType 상황에서도 이러한 규정이 강화됩니다.

접근성

포함하는 형식이 표시되는 컨텍스트에서 멤버를 설정할 수 없는 경우 필요한 멤버를 표시하는 것은 오류입니다.

  • 멤버가 필드이면 readonly이 될 수 없습니다.
  • 멤버가 속성인 경우 적어도 멤버의 포함 형식만큼 액세스할 수 있는 setter 또는 initer가 있어야 합니다.

즉, 다음과 같은 경우는 허용되지 않습니다.

interface I
{
    int Prop1 { get; }
}
public class Base
{
    public virtual int Prop2 { get; set; }

    protected required int _field; // Error: _field is not at least as visible as Base. Open question below about the protected constructor scenario

    public required readonly int _field2; // Error: required fields cannot be readonly
    protected Base() { }

    protected class Inner
    {
        protected required int PropInner { get; set; } // Error: PropInner cannot be set inside Base or Derived
    }
}
public class Derived : Base, I
{
    required int I.Prop1 { get; } // Error: explicit interface implementions cannot be required as they cannot be set in an object initializer

    public required override int Prop2 { get; set; } // Error: this property is hidden by Derived.Prop2 and cannot be set in an object initializer
    public new int Prop2 { get; }

    public required int Prop3 { get; } // Error: Required member must have a setter or initer

    public required int Prop4 { get; internal set; } // Error: Required member setter must be at least as visible as the constructor of Derived
}

소비자가 해당 멤버를 더 이상 설정할 수 없으므로 required 멤버를 숨기는 것은 오류입니다.

required 멤버를 재정의할 때 메서드 서명에 required 키워드를 포함해야 합니다. 이렇게 하여 나중에 속성의 요구 사항을 재정의를 통해 취소할 수 있는 경우를 대비하여 설계의 여유 공간을 확보할 수 있습니다.

멤버가 기본 형식에서 required이 아니지만 required으로 표시되는 경우, 재정의가 허용됩니다. 파생 형식에 필요한 멤버 목록에 이렇게 표시된 멤버가 추가됩니다.

형식은 필요한 가상 속성을 재정의할 수 있습니다. 즉, 기본 가상 속성에 스토리지가 있고 파생된 형식이 해당 속성의 기본 구현에 액세스하려고 하면 초기화되지 않은 스토리지를 관찰할 수 있습니다. NB: 이것은 일반적인 C# 안티 패턴이며, 우리는이 제안이 그것을 해결하기 위해 시도해야한다고 생각하지 않습니다.

nullable 분석에 미치는 영향

required로 표시된 멤버는 생성자 끝에서 유효한 nullable 상태로 초기화되도록 요구되지 않습니다. 이 유형과 모든 기본 유형의 모든 required 멤버는 this특성이 있는 base 또는 SetsRequiredMembersAttribute 생성자에 연결되지 않는 한, 해당 유형의 생성자 시작 시점에서 null 허용 분석에 의해 기본값으로 간주됩니다.

Nullable 분석은 required특성이 지정된 생성자의 끝에 유효한 nullable 상태가 없는 현재 및 기본 형식의 모든 SetsRequiredMembersAttribute 멤버에 대해 경고합니다.

#nullable enable
public class Base
{
    public required string Prop1 { get; set; }

    public Base() {}

    [SetsRequiredMembers]
    public Base(int unused) { Prop1 = ""; }
}
public class Derived : Base
{
    public required string Prop2 { get; set; }

    [SetsRequiredMembers]
    public Derived() : base()
    {
    } // Warning: Prop1 and Prop2 are possibly null.

    [SetsRequiredMembers]
    public Derived(int unused) : base()
    {
        Prop1.ToString(); // Warning: possibly null dereference
        Prop2.ToString(); // Warning: possibly null dereference
    }

    [SetsRequiredMembers]
    public Derived(int unused, int unused2) : this()
    {
        Prop1.ToString(); // Ok
        Prop2.ToString(); // Ok
    }

    [SetsRequiredMembers]
    public Derived(int unused1, int unused2, int unused3) : base(unused1)
    {
        Prop1.ToString(); // Ok
        Prop2.ToString(); // Warning: possibly null dereference
    }
}

메타데이터 표현

다음 2개 특성은 C# 컴파일러에 알려져 있으며 이 기능이 작동하는 데 필요합니다.

namespace System.Runtime.CompilerServices
{
    [AttributeUsage(AttributeTargets.Class | AttributeTargets.Struct | AttributeTargets.Field | AttributeTargets.Property, AllowMultiple = false, Inherited = false)]
    public sealed class RequiredMemberAttribute : Attribute
    {
        public RequiredMemberAttribute() {}
    }
}

namespace System.Diagnostics.CodeAnalysis
{
    [AttributeUsage(AttributeTargets.Constructor, AllowMultiple = false, Inherited = false)]
    public sealed class SetsRequiredMembersAttribute : Attribute
    {
        public SetsRequiredMembersAttribute() {}
    }
}

형식에 RequiredMemberAttribute 수동으로 적용하는 것은 오류입니다.

required 표시된 멤버에는 RequiredMemberAttribute 적용됩니다. 또한 이러한 멤버를 정의하는 모든 형식은 이 형식에 필요한 멤버가 있음을 나타내는 표식으로 RequiredMemberAttribute표시됩니다. B 형식이 A에서 파생되고 Arequired 멤버를 정의하지만 B가 새 멤버를 추가하지 않거나 기존 required 멤버를 재정의하지 않으면 BRequiredMemberAttribute로 표시되지 않습니다. B필요한 멤버가 있는지 여부를 완전히 확인하려면 전체 상속 계층을 확인해야 합니다.

required이 적용되지 않은 SetsRequiredMembersAttribute 멤버를 가진 형식의 생성자는 두 가지 속성으로 표시됩니다.

  1. System.Runtime.CompilerServices.CompilerFeatureRequiredAttribute 기능 이름이 "RequiredMembers"인 경우.
  2. 문자열 System.ObsoleteAttribute를 사용하여 "Types with required members are not supported in this version of your compiler"을 대체하고, 해당 속성은 오류로 표시되어 이전 컴파일러가 이러한 생성자를 사용하지 못하도록 합니다.

이진 호환성을 유지하는 것이 목표이기 때문에 우리는 여기서 modreq을 사용하지 않고 있습니다. 만약 형식에서 마지막 required 속성이 제거되면, 컴파일러는 더 이상 modreq를 합성하지 않게 되어, 이는 이진 호환성을 깨뜨리는 변화가 되며, 이로 인해 모든 사용자가 다시 컴파일해야 합니다. required 멤버를 이해하는 컴파일러는 이 사용되지 않는 특성을 무시합니다. 멤버는 기본 형식에서도 사용할 수 있습니다. 현재 형식에 새 required 멤버가 없더라도 기본 형식에 required 멤버가 있는 경우 이 Obsolete 특성이 생성됩니다. 생성자에 이미 Obsolete 특성이 있는 경우 추가 Obsolete 특성이 생성되지 않습니다.

ObsoleteAttributeCompilerFeatureRequiredAttribute 모두 사용합니다. 이 릴리스는 새 릴리스이고 이전 컴파일러는 이를 이해하지 못하기 때문입니다. 앞으로는 ObsoleteAttribute 삭제하거나 새 기능을 보호하는 데 사용하지 않을 수 있지만 지금은 완전한 보호를 위해 둘 다 필요합니다.

지정된 형식 required와 모든 기본 형식을 포함하여 R 멤버 T의 전체 목록을 작성하기 위해 다음 알고리즘이 실행됩니다.

  1. 모든 Tb에 대해 T을 시작으로 object에 도달할 때까지 기본 형식 체인을 따라 차례로 작업합니다.
  2. Tb이(가) RequiredMemberAttribute으로 표시된 경우, Tb으로 표시된 모든 RequiredMemberAttribute 멤버는 Rb에 모입니다.
    1. Ri의 각 Rb에 대해 RiR의 멤버들에 의해 재정의되는 경우, 그것은 건너뛰게 됩니다.
    2. 그렇지 않으면, Ri이/가 R멤버에 의해 숨겨지면 필요한 멤버의 조회가 실패하며 추가 단계를 수행하지 않습니다. T 특성이 없는 SetsRequiredMembers 생성자를 호출하면 오류가 발생합니다.
    3. 그렇지 않으면 RiR에 추가됩니다.

질문 열기

중첩 멤버 초기화기

중첩 멤버 이니셜라이저에 대한 적용 메커니즘은 무엇인가요? 완전히 금지될까요?

class Range
{
    public required Location Start { get; init; }
    public required Location End { get; init; }
}

class Location
{
    public required int Column { get; init; }
    public required int Line { get; init; }
}

_ = new Range { Start = { Column = 0, Line = 0 }, End = { Column = 1, Line = 0 } } // Would this be allowed if Location is a struct type?
_ = new Range { Start = new Location { Column = 0, Line = 0 }, End = new Location { Column = 1, Line = 0 } } // Or would this form be necessary instead?

논의된 질문

init 절에 대한 집행 정도

init 절 기능은 C# 11에서 구현되지 않았습니다. 활발한 제안으로 남아있다.

이니셜라이저 없이 init 절에 지정된 멤버가 모든 멤버를 초기화하도록 엄격하게 적용합니까? 우리가 그렇게 할 가능성이 높아 보입니다, 그렇지 않으면 우리는 쉽게 실패에 빠지는 함정을 만들게 됩니다. 그러나 C# 9에서 MemberNotNull으로 해결한 것과 동일한 문제를 다시 도입할 위험도 있습니다. 이를 엄격하게 적용하려면 도우미 메서드가 멤버를 설정함을 나타내는 방법이 필요할 수 있습니다. 이에 대해 설명한 몇 가지 가능한 구문은 다음과 같습니다.

  • init 메서드를 허용합니다. 이러한 메서드는 생성자 또는 다른 init 메서드에서만 호출할 수 있으며, 마치 생성자 안에서처럼 this에 액세스할 수 있습니다. 즉, readonlyinit 필드/속성을 설정할 수 있습니다. 이러한 메서드에서 init 절과 결합할 수 있습니다. init 절에 있는 멤버가 메서드/생성자의 본문에서 확실히 할당된 경우, init 절이 충족된 것으로 간주됩니다. 멤버를 포함하는 init 절을 사용하여 메서드를 호출하면 해당 멤버에 할당하는 것으로 간주됩니다. 이 방향을 현재 또는 미래에 추구하기로 우리가 결정한다면, init 절에서 init을 생성자의 키워드로 사용하지 않는 것이 혼란을 피하는 것일 가능성이 높다고 생각됩니다.
  • ! 연산자가 경고/오류를 명시적으로 억제할 수 있도록 허용합니다. 공유 메서드와 같이 복잡한 방식으로 멤버를 초기화하는 경우 사용자는 init 절에 ! 추가하여 컴파일러가 초기화를 확인하지 않아야 함을 나타낼 수 있습니다.

결론: 토론 후 우리는 ! 연산자의 아이디어를 좋아한다. 사용자가 init 메서드 주변에 큰 디자인 문제를 일으키지 않으면서 멤버 X 또는 Y로 모든 메서드를 주석 처리하는 것 없이 더 복잡한 시나리오에 대해 의도적으로 작업을 수행할 수 있습니다. !는 이미 null 가능성 경고 억제를 위해 사용되고 있으며, 다른 영역에서 컴파일러에게 자신이 더 우수하다는 것을 알리는 자연스러운 구문 확장으로 선택되었습니다.

필수 인터페이스 멤버

이 제안에서는 인터페이스가 멤버를 필요에 따라 표시할 수 없습니다. 이렇게 하면 우리에게 있어 지금 제네릭의 new() 및 인터페이스 제약과 관련된 복잡한 시나리오를 해결할 필요를 없애주며, 공장 설계 패턴 및 제네릭 생성과 직접적으로 연관되어 있습니다. 이 영역에 디자인 공간을 확보하기 위해, 인터페이스에서는 required을/를 금지하며, 필수_멤버_목록이 포함된 형식이 new()으로 제한된 형식 매개변수로 사용되는 것을 금지합니다. 공장의 일반 건설 시나리오를 좀 더 광범위하게 살펴보려면 이 문제를 다시 살펴볼 수 있습니다.

구문 질문

init 절 기능은 C# 11에서 구현되지 않았습니다. 활발한 제안으로 남아있다.

  • init 올바른 단어인가요? 팩터리로 다시 사용하고 싶거나 접두사 수정자로 init 메서드를 활성화하려는 경우, 생성자에 후위 수정자로서 init이 충돌할 수 있습니다. 기타 가능성:
    • set
  • required 모든 멤버가 초기화되도록 지정하는 데 적합한 한정자입니까? 다른 제안:
    • default
    • all
    • 함께 하는 ! 복잡한 논리를 나타내려면
  • base / thisinit사이에 구분 기호가 필요한가요?
    • : 구분 기호
    • ',' 구분 기호
  • required 올바른 한정자입니까? 제안 된 다른 대안:
    • req
    • require
    • mustinit
    • must
    • explicit

결론: 저희는 현재 init 생성자 절을 제거했고, required을 속성 한정자로 사용하여 진행하고 있습니다.

초기화 절 제한

init 절 기능은 C# 11에서 구현되지 않았습니다. 활발한 제안으로 남아있다.

init 절에서 this 대한 액세스를 허용해야 하나요? init 할당이 생성자 자체에서 멤버를 할당하기 위한 약식이 되도록 하려면 그렇게 하는 것이 좋을 것 같습니다.

또한 base() 같은 새 범위를 만들나요, 아니면 메서드 본문과 동일한 범위를 공유하나요? 이는 init 절에서 접근하고자 할 수도 있는 로컬 함수와 같은 것들이나 init 식이 out 매개변수를 통해 변수를 도입하면서 이름 가리기가 발생하는 경우에 특히 중요합니다.

결론: init 절이 제거되었습니다.

접근성 요구 사항 및 init

init 절 기능은 C# 11에서 구현되지 않았습니다. 활발한 제안으로 남아있다.

init 절이 포함된 이 제안 버전에서는 다음 시나리오를 수행할 수 있다고 설명했습니다.

public class Base
{
    protected required int _field;

    protected Base() {} // Contract required that _field is set
}
public class Derived : Base
{
    public Derived() : init(_field = 1) // Contract is fulfilled and _field is removed from the required members list
    {
    }
}

그러나 이 시점에서 제안에서 init 절을 제거했으므로 이 시나리오를 제한된 방식으로 허용할지 여부를 결정해야 합니다. 다음과 같은 옵션이 있습니다.

  1. 시나리오를 허용하지 않습니다. 이는 가장 보수적인 접근 방식이며, 접근성 규칙은 현재 이 가정을 염두에 두고 작성되었습니다. 규칙은 필요한 모든 멤버가 최소한 포함하는 형식만큼 표시되어야 한다는 것입니다.
  2. 모든 생성자가 다음 중 하나일 것을 요구합니다.
    1. 가장 눈에 잘 띄지 않는 필수 멤버보다 더 이상 표시되지 않습니다.
    2. 생성자에 SetsRequiredMembersAttribute 적용하세요. 이로 인해 생성자를 볼 수 있는 모든 사용자가 내보내는 항목을 모두 설정할 수 있거나, 반대로 설정할 것이 없도록 합니다. 이는 정적 Create 메서드나 유사한 빌더를 통해서만 생성되는 유형에 유용할 수 있지만, 그 유틸리티는 전반적으로 제한적인 것처럼 보입니다.
  3. 이전에 LDM 설명한 대로 제안서에 대한 계약의 특정 부분을 제거하는 방법을 읽었습니다.

결론: 옵션 1에서는 모든 필요한 멤버가 포함된 타입만큼 또는 그 이상으로 가시적이어야 합니다.

규칙 재정의

현재 사양에 따르면 required 키워드를 복사해야 하며, 재정의를 통해 멤버 이 더 많은를 필요로 할 수 있지만, 필요성을 줄일 수는 없습니다. 우리가 하고 싶은 일인가요? 요구 사항 제거를 허용하려면 현재 제안하는 것보다 더 많은 계약 수정 능력이 필요합니다.

결론: required를 오버라이드에 추가할 수 있습니다. 재정의된 멤버가 required인 경우, 재정의하는 멤버도 required해야 합니다.

대체 메타데이터 표현

확장 메서드에서 페이지를 가져와 메타데이터 표현에 대해 다른 접근 방식을 취할 수도 있습니다. 형식에 필요한 멤버가 포함되어 있음을 나타내기 위해 형식에 RequiredMemberAttribute 배치한 다음 필요한 각 멤버에 RequiredMemberAttribute 배치할 수 있습니다. 이렇게 하면 조회 시퀀스가 간소화됩니다(멤버 조회를 수행할 필요가 없고 특성이 있는 멤버만 찾습니다).

결론: 대체 승인.

메타데이터 표현

메타데이터 표현이 승인되어야 합니다. 또한 이러한 특성을 BCL에 포함할지 여부를 결정해야 합니다.

  1. RequiredMemberAttribute의 경우, 이 특성은 nullable/nint/튜플 멤버 이름에 사용하는 일반적인 내장 특성과 더 유사하며, 사용자가 C#에서 수동으로 적용하지 않습니다. 그러나 다른 언어에서 이 특성을 수동으로 적용하려고 할 수 있습니다.
  2. 반면에 SetsRequiredMembersAttribute소비자가 직접 사용하므로 BCL에 있을 가능성이 높습니다.

이전 섹션의 대체 표현을 따르면, RequiredMemberAttribute의 해석이 달라질 수 있습니다. 이는 nint/nullable/튜플 멤버 이름에 대한 일반적인 포함 속성과 유사하기보다는, 확장 메서드가 도입된 이후부터 프레임워크에 포함되어 있던 System.Runtime.CompilerServices.ExtensionAttribute에 더 가깝습니다.

결론: BCL에 두 특성을 모두 넣습니다.

경고 및 오류

필수 멤버를 설정하지 않으면 경고 또는 오류가 되어야 하나? Activator.CreateInstance(typeof(C)) 또는 이와 유사한 방법을 통해 시스템을 속일 수 있습니다. 즉, 모든 속성이 항상 설정되도록 완전히 보장하지 못할 수 있습니다. 또한 일반적으로 오류를 허용하지 않는 !사용하여 생성자 사이트에서 진단을 표시하지 않도록 허용합니다. 그러나 이 기능은 사용자가 초기화 후 이러한 멤버를 설정하려고 하면 오류가 발생하지만 리플렉션으로 우회할 수 있다는 측면에서 읽기 전용 필드 또는 init 속성과 유사합니다.

결론: 오류입니다.