Condividi tramite


Membri obbligatori

Nota

Questo articolo è una specifica delle funzionalità. La specifica funge da documento di progettazione per la funzionalità. Include le modifiche specifiche proposte, insieme alle informazioni necessarie durante la progettazione e lo sviluppo della funzionalità. Questi articoli vengono pubblicati fino a quando le modifiche specifiche proposte non vengono completate e incorporate nella specifica ECMA corrente.

Potrebbero verificarsi alcune discrepanze tra la specifica di funzionalità e l'implementazione completata. Tali differenze vengono acquisite nelle note language design meeting (LDM) pertinenti.

Nell'articolo sulle specifiche di , sono disponibili ulteriori informazioni sul processo di adozione degli speclet delle funzionalità nello standard del linguaggio C#.

Sommario

Questa proposta aggiunge un modo per specificare che è necessario impostare una proprietà o un campo durante l'inizializzazione dell'oggetto, forzando l'autore dell'istanza a fornire un valore iniziale per il membro in un inizializzatore di oggetto nel sito di creazione.

Motivazione

Le gerarchie degli oggetti oggi richiedono una significativa quantità di codice standard per trasferire i dati attraverso tutti i livelli della gerarchia. Si esaminerà ora una semplice gerarchia che include un Person come potrebbe essere definito in C# 8:

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;
    }
}

C'è un sacco di ripetizioni in corso qui:

  1. Nella radice della gerarchia, il tipo di ogni proprietà doveva essere ripetuto due volte e il nome doveva essere ripetuto quattro volte.
  2. A livello derivato, il tipo di ogni proprietà ereditata doveva essere ripetuto una volta e il nome doveva essere ripetuto due volte.

Si tratta di una gerarchia semplice con 3 proprietà e 1 livello di ereditarietà, ma molti esempi reali di questi tipi di gerarchia vanno molti livelli più in profondità, accumulando un numero sempre maggiore di proprietà da trasmettere man mano che si sviluppano. Roslyn è una di queste codebase, ad esempio nei vari tipi di albero che costituiscono i nostri CST e AST. Questo annidamento è così noioso che abbiamo generatori di codice per creare i costruttori e le definizioni di questi tipi, e molti clienti adottano approcci simili al problema. C# 9 introduce i record, che per alcuni scenari possono migliorare la situazione.

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

recordelimina la prima fonte di duplicazione, ma la seconda fonte di duplicazione rimane invariata: purtroppo, questa è la fonte di duplicazione che cresce man mano che la gerarchia cresce, ed è la parte più dolorosa della duplicazione da correggere dopo aver apportato una modifica nella gerarchia, poiché richiede di inseguire la gerarchia in tutte le sue posizioni, possibilmente anche tra progetti e potenzialmente in grado di interrompere i consumatori.

Come soluzione alternativa per evitare questa duplicazione, abbiamo a lungo visto i consumatori utilizzare gli inizializzatori di oggetti come un modo per evitare di scrivere costruttori. Prima di C# 9, tuttavia, questo aveva 2 principali svantaggi:

  1. La gerarchia di oggetti deve essere completamente modificabile, con funzioni di accesso set in ogni proprietà.
  2. Non è possibile garantire che ogni istanza di un oggetto dal grafo imposti ogni componente.

C# 9 ha risolto di nuovo il primo problema, introducendo la funzione di accesso init: con essa, queste proprietà possono essere impostate in fase di creazione/inizializzazione degli oggetti, ma non successivamente. Tuttavia, è ancora presente il secondo problema: le proprietà in C# sono state facoltative a partire da C# 1.0. I tipi di riferimento nullable, introdotti in C# 8.0, hanno affrontato parte di questo problema: se un costruttore non inizializza una proprietà di tipo riferimento non-nullable, ne viene avvisato l'utente. Tuttavia, questo non risolve il problema: l'utente qui vuole evitare di ripetere ampie sezioni del tipo nel costruttore e desidera passare al requisito per impostare le proprietà sui propri consumatori. Inoltre, non vengono forniti avvisi su ID relativi a Student, poiché si tratta di un tipo di valore. Questi scenari sono estremamente comuni negli ORM del modello di database, come EF Core, che devono avere un costruttore pubblico senza parametri, ma determinano quindi la nullabilità delle righe in base alla nullabilità delle proprietà.

Questa proposta cerca di risolvere questi problemi introducendo una nuova funzionalità per C#: i membri richiesti. I membri obbligatori dovranno essere inizializzati dai consumatori, anziché dall'autore del tipo, con varie personalizzazioni che consentono flessibilità per più costruttori e altri scenari.

Progettazione dettagliata

class, structe i tipi di record ottengono la possibilità di dichiarare un required_member_list. Questo elenco è l'elenco di tutte le proprietà e i campi di un tipo ritenuti necessarie che devono essere inizializzati nel momento della creazione e dell'inizializzazione di un'istanza del tipo. I tipi ereditano automaticamente questi elenchi dai loro tipi base, offrendo un'esperienza fluida che elimina codice ridondante e ripetitivo.

modificatore required

Aggiungiamo 'required' all'elenco dei modificatori in field_modifier e property_modifier. La required_member_list di un tipo è costituita da tutti i membri su cui è stato applicato required. Di conseguenza, il tipo Person precedente ora appare così:

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; }
}

Tutti i costruttori di un tipo con un required_member_list annunciano automaticamente un contratto secondo il quale i consumatori del tipo devono inizializzare tutte le proprietà nell'elenco. Si tratta di un errore per un costruttore per annunciare un contratto che richiede un membro che non sia almeno accessibile come il costruttore stesso. Per esempio:

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 è valido solo nei tipi class, structe record. Non è valido nei tipi di interface. required non può essere combinato con i modificatori seguenti:

  • fixed
  • ref readonly
  • ref
  • const
  • static

required non può essere applicato agli indicizzatori.

Il compilatore genererà un avviso quando Obsolete viene applicato a un membro obbligatorio di un tipo e:

  1. Il tipo non è contrassegnato Obsoleteo
  2. Qualsiasi costruttore non attribuito con SetsRequiredMembersAttribute non è segnato con Obsolete.

SetsRequiredMembersAttribute

Tutti i costruttori di un tipo con membri obbligatori, o il cui tipo di base specifica membri obbligatori, devono avere questi membri impostati da un utente quando il costruttore viene chiamato. Per esentare i costruttori da questo requisito, un costruttore può essere attribuito con SetsRequiredMembersAttribute, che rimuove questi requisiti. Il corpo del costruttore non viene convalidato per garantire l'impostazione definitiva dei membri richiesti del tipo.

SetsRequiredMembersAttribute rimuove tutti i requisiti da un costruttore e tali requisiti non vengono controllati in alcun modo per la validità. NB: questa è l'uscita di emergenza se è necessario ereditare da un tipo con un elenco di membri obbligatori non validi: contrassegnare il costruttore di quel tipo con SetsRequiredMembersAttribute, e non verranno segnalati errori.

Se un costruttore C si collega a un costruttore base o this attribuito con SetsRequiredMembersAttribute, C deve essere anch'esso attribuito con SetsRequiredMembersAttribute.

Per i tipi di record, emetteremo SetsRequiredMembersAttribute nel costruttore di copia sintetico di un record se il tipo di record o uno dei suoi tipi di base ha membri richiesti.

NB: Una versione precedente di questa proposta aveva un meta linguaggio più ampio riguardante l'inizializzazione, consentendo l'aggiunta e la rimozione di singoli membri obbligatori da un costruttore, oltre alla convalida che il costruttore impostasse tutti i membri richiesti. Questo è stato considerato troppo complesso per la versione iniziale e rimosso. È possibile esaminare l'aggiunta di contratti e modifiche più complessi come funzionalità successiva.

Rinforzo

Per ogni costruttore Ci di tipo T con membri obbligatori R, i consumer che chiamano Ci devono eseguire una delle operazioni seguenti:

  • Imposta tutti i membri di R in un object_initializer sull'object_creation_expression.
  • Oppure, impostare tutti i membri di R tramite la sezione named_argument_list di un attribute_target.

a meno che Ci non sia attribuito con SetsRequiredMembers.

Se il contesto corrente non consente un inizializzatore_di_oggetto o non è un bersaglio_di_attributo, e Ci non ha l'attributo SetsRequiredMembers, allora è un errore chiamare Ci.

vincolo new()

Un tipo con un costruttore senza parametri che dichiara un contratto non può essere sostituito con un parametro di tipo che è vincolato a new(), poiché non esiste alcun modo per l'instanziazione generica di garantire che i requisiti siano soddisfatti.

struct defaults

I membri obbligatori non vengono imposti alle istanze di tipi di struct creati con default o default(StructType). Vengono applicate per le istanze di struct create con new StructType(), anche quando StructType non dispone di un costruttore senza parametri e viene usato il costruttore struct predefinito.

Accessibilità

È un errore contrassegnare un membro come obbligatorio se il membro non può essere impostato in nessun contesto in cui il tipo è visibile.

  • Se il membro è un campo, non può essere readonly.
  • Se il membro è una proprietà, deve avere un setter o un initer la cui accessibilità sia almeno pari a quella del tipo contenitore del membro.

Ciò significa che i casi seguenti non sono consentiti:

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
}

Nascondere un membro required è un errore, in quanto tale membro non può più essere impostato da un consumatore.

Quando si esegue l'override di un membro required, la parola chiave required deve essere inclusa nella firma del metodo. Questa operazione viene eseguita in modo che, se in futuro desiderassimo consentire di rendere non obbligatoria una proprietà tramite un override, abbiamo spazio di progettazione per farlo.

Le sovrascritture sono consentite per contrassegnare un membro required dove non era required nel tipo di base. Un membro così contrassegnato viene aggiunto all'elenco dei membri richiesti del tipo derivato.

I tipi possono eseguire l'override delle proprietà virtuali obbligatorie. Ciò significa che se la proprietà virtuale di base ha spazio di archiviazione e il tipo derivato tenta di accedere all'implementazione di base di tale proprietà, potrebbe osservare l'archiviazione non inizializzata. NB: si tratta di un anti-modello C# generale e non pensiamo che questa proposta debba tentare di risolverla.

Effetto sull'analisi che ammette valori nulli

I membri contrassegnati required non sono tenuti a essere inizializzati in uno stato annullabile valido alla fine di un costruttore. Tutti i membri required di questo tipo e di qualsiasi tipo base sono considerati dall'analisi nullable come predefiniti all'inizio di un costruttore di quel tipo, a meno che esso non sia in concatenazione con un costruttore this o base attribuito con SetsRequiredMembersAttribute.

L'analisi nullable avvisa su tutti i membri required dei tipi correnti e di base che non hanno uno stato nullable valido alla fine di un costruttore con attributo 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
    }
}

Rappresentazione dei metadati

I 2 attributi seguenti sono noti al compilatore C# e sono necessari per il funzionamento di questa funzionalità:

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() {}
    }
}

È un errore commettere l'applicazione manuale di RequiredMemberAttribute a un tipo.

A qualsiasi membro contrassegnato con required viene applicato un RequiredMemberAttribute. Inoltre, qualsiasi tipo che definisce tali membri è contrassegnato con RequiredMemberAttribute, come marcatore per indicare che in questo tipo sono presenti membri necessari. Si noti che se il tipo B deriva da Ae A definisce required membri, ma B non aggiunge né sovrascrive alcun membro required esistente, B non verrà contrassegnato con un RequiredMemberAttribute. Per determinare con precisione se sono presenti membri richiesti in B, è necessario verificare l'intera gerarchia di ereditarietà.

Qualsiasi costruttore in un tipo con membri required a cui non è applicato SetsRequiredMembersAttribute è contrassegnato con due attributi:

  1. System.Runtime.CompilerServices.CompilerFeatureRequiredAttribute con il nome della funzionalità "RequiredMembers".
  2. System.ObsoleteAttribute con la stringa "Types with required members are not supported in this version of your compiler"e l'attributo viene contrassegnato come errore per evitare che eventuali compilatori più vecchi utilizzino questi costruttori.

In questo caso non usiamo un modreq perché è un obiettivo mantenere la compatibilità binaria: se l'ultima proprietà required venisse rimossa da un tipo, il compilatore non sintetizzerebbe più questo modreq, il che costituirebbe una modifica a livello binario e tutti i consumatori dovrebbero essere ricompilati. Un compilatore che riconosce i membri required ignorerà questo attributo obsoleto. Si noti che i membri possono provenire anche dai tipi di base: anche se non sono presenti nuovi membri required nel tipo corrente, se un tipo di base ha required membri, verrà generato questo attributo Obsolete. Se il costruttore dispone già di un attributo Obsolete, non verrà generato alcun attributo Obsolete aggiuntivo.

Vengono usati sia ObsoleteAttribute che CompilerFeatureRequiredAttribute perché quest'ultimo è una nuova versione e i compilatori meno recenti non lo capiscono. In futuro, potremmo eliminare il ObsoleteAttribute e/o meno usarlo per proteggere le nuove funzionalità, ma per ora abbiamo bisogno di entrambi per la protezione completa.

Per compilare l'elenco completo dei membri requiredR per un determinato tipo T, inclusi tutti i tipi di base, viene eseguito l'algoritmo seguente:

  1. Per ogni Tb, partendo da T e attraversando la catena dei tipi di base fino a raggiungere object.
  2. Se Tb è contrassegnato con RequiredMemberAttribute, tutti i membri di Tb contrassegnati con RequiredMemberAttribute vengono raccolti in Rb
    1. Per ogni Ri in Rb, se Ri viene sottoposto a override da qualsiasi membro di R, viene ignorato.
    2. In caso contrario, se un Ri è nascosto da un membro di R, la ricerca dei membri necessari ha esito negativo e non vengono eseguiti ulteriori passaggi. Chiamare un costruttore qualsiasi di T non associato a SetsRequiredMembers genera un errore.
    3. In caso contrario, Ri viene aggiunto a R.

Domande aperte

Inizializzatori di membri annidati

Quali saranno i meccanismi di attuazione per gli inizializzatori di membri annidati? Saranno completamente vietati?

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?

Discussioni sulle domande

Livello di applicazione per le clausole di init

La funzionalità della clausola init non è stata implementata in C# 11. Rimane una proposta attiva.

Applichiamo rigorosamente che i membri specificati in una clausola init senza un inizializzatore debbano avere tutti i membri inizializzati? Sembra probabile che lo facciamo, altrimenti creiamo una facile trappola di fallimento. Tuttavia, si corre anche il rischio di reintrodurre gli stessi problemi risolti con MemberNotNull in C# 9. Se vogliamo applicare rigorosamente questa regola, probabilmente avremo bisogno di un modo affinché un metodo ausiliario indichi che imposta un membro. Alcune possibili sintassi descritte per questo argomento:

  • Consenti metodi di init. Questi metodi possono essere chiamati solo da un costruttore o da un altro metodo init, e possono accedere this come se si trovassero nel costruttore (ad esempio impostano i campi e le proprietà readonly e init). Questa operazione può essere combinata con clausole init su tali metodi. Una clausola init verrebbe considerata soddisfatta se il membro nella clausola sia assegnato con certezza nel corpo del metodo/costruttore. La chiamata di un metodo con una clausola init che include un membro viene conteggiata come assegnazione a tale membro. Se decideremo che si tratta di un percorso che vogliamo perseguire, ora o in futuro, sembra probabile che non dovremmo usare init come parola chiave per la clausola init di un costruttore, in quanto ciò creerebbe confusione.
  • Consentire all'operatore ! di eliminare in modo esplicito l'avviso o l'errore. Se si inizializza un membro in modo complesso, ad esempio in un metodo condiviso, l'utente può aggiungere un ! alla clausola init per indicare che il compilatore non deve controllare l'inizializzazione.

Conclusione: Dopo la discussione, ci piace l'idea dell'operatore !. Consente all'utente di essere intenzionale su scenari più complessi, senza nemmeno creare un grande vuoto di progettazione attorno ai metodi init e annotando ogni metodo come impostazione dei membri X o Y. ! è stato scelto perché è già utilizzato per sopprimere gli avvisi sui riferimenti di tipo nullable e usarlo per indicare al compilatore "sono più intelligente di te" in un altro contesto è un'estensione naturale del formato della sintassi.

Membri dell'interfaccia necessari

Questa proposta non consente alle interfacce di contrassegnare i membri in base alle esigenze. Ciò ci impedisce di dover individuare scenari complessi relativi a new() e ai vincoli di interfaccia nei generici in questo momento, ed è direttamente correlato sia alle factory che alla costruzione generica. In modo da assicurarsi di disporre di spazio di progettazione in questa area, si vieta l'uso di required nelle interfacce e si impedisce ai tipi con required_member_lists di essere sostituiti ai parametri di tipo con vincolo a new(). Quando si vuole esaminare in modo più ampio gli scenari di costruzione generici con le fabbriche, è possibile rivedere questo problema.

Domande sulla sintassi

La funzionalità della clausola init non è stata implementata in C# 11. Rimane una proposta attiva.

  • È init la parola giusta? init come modificatore di suffisso sul costruttore potrebbe interferire se mai volessimo riutilizzarlo per le factory e abilitare anche i metodi init con un modificatore di prefisso. Altre possibilità:
    • set
  • È required il modificatore corretto per specificare che tutti i membri vengono inizializzati? Altri suggeriti:
    • default
    • all
    • Con un'accompagnamento! per indicare la logica complessa
  • È necessario un separatore tra il base/this e il init?
    • separatore :
    • Separatore ','
  • È required modificatore corretto? Altre alternative suggerite:
    • req
    • require
    • mustinit
    • must
    • explicit

Conclusione: è stata rimossa la clausola del costruttore init per il momento e si procede con required come modificatore di proprietà.

Restrizioni delle clausole Init

La funzionalità della clausola init non è stata implementata in C# 11. Rimane una proposta attiva.

È necessario consentire l'accesso a this nella clausola init? Se si vuole che l'assegnazione in init sia una sintassi abbreviata per assegnare il membro nel costruttore stesso, sembra che dovremmo.

Inoltre, crea un nuovo ambito, come base(), o condivide lo stesso ambito del corpo del metodo? Ciò è particolarmente importante per elementi come le funzioni locali, a cui la clausola init può voler accedere o per l'ombreggiatura dei nomi, se un'espressione init introduce una variabile tramite out parametro.

Conclusione: init clausola è stata rimossa.

Requisiti di accessibilità e init

La funzionalità della clausola init non è stata implementata in C# 11. Rimane una proposta attiva.

Nelle versioni di questa proposta con la clausola init si è parlato di essere in grado di avere lo scenario seguente:

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
    {
    }
}

Tuttavia, abbiamo rimosso la clausola init dalla proposta, quindi dobbiamo decidere se consentire questo scenario in modo limitato. Le opzioni disponibili sono:

  1. Non consentire lo scenario. Questo è l'approccio più conservativo e le regole del accessibility sono attualmente scritte tenendo presente questo presupposto. La regola è che ogni membro necessario deve essere visibile almeno quanto il tipo che lo contiene.
  2. Richiedere che tutti i costruttori siano i seguenti:
    1. Non più visibile del membro obbligatorio meno visibile.
    2. Applicare il SetsRequiredMembersAttribute al costruttore. In questo modo tutti gli utenti che hanno accesso a un costruttore possono impostare tutte le cose che esso esporta, oppure non c'è niente da impostare. Questo può essere utile per i tipi che vengono creati esclusivamente tramite metodi statici Create o costruttori simili, ma l'utilità sembra complessivamente limitata.
  3. Reinserire un modo per rimuovere parti specifiche del contratto nella proposta, come discusso precedentemente in LDM.

Conclusione: Opzione 1, tutti i membri obbligatori devono essere visibili almeno quanto il tipo contenitore.

Sovrascrivere le regole

La specifica corrente indica che la parola chiave required deve essere copiata e che gli override possono rendere un membro più necessario, ma non meno. È quello che vogliamo fare? Consentire la rimozione dei requisiti richiede più capacità di modifica del contratto rispetto a quelle attualmente proposte.

Conclusione: è consentita l'aggiunta di required all'override. Se il membro sottoposto a override è required, il membro che effettua l'override deve essere anche required.

Rappresentazione alternativa dei metadati

È anche possibile adottare un approccio diverso alla rappresentazione dei metadati, ispirandosi ai metodi di estensione. È possibile inserire un RequiredMemberAttribute sul tipo per indicare che il tipo contiene membri obbligatori e quindi inserire un RequiredMemberAttribute su ogni membro richiesto. In questo modo si semplifica la sequenza di ricerca (non è necessario eseguire la ricerca dei membri, è sufficiente cercare i membri con l'attributo ).

Conclusione: alternativa approvata.

Rappresentazione dei metadati

La rappresentazione dei metadati deve essere approvata. È inoltre necessario decidere se questi attributi devono essere inclusi nel BCL.

  1. Per RequiredMemberAttribute, questo attributo è più simile agli attributi generali incorporati che utilizziamo per i nomi dei membri nullable/nint/tuple e non sarà applicato manualmente dall'utente in C#. È tuttavia possibile che altri linguaggi vogliano applicare manualmente questo attributo.
  2. SetsRequiredMembersAttribute, d'altra parte, viene usato direttamente dai consumatori e pertanto dovrebbe probabilmente trovarsi nel BCL.

Se consideriamo la rappresentazione alternativa nella sezione precedente, questo potrebbe modificare il calcolo relativo a RequiredMemberAttribute: invece di essere simile agli attributi generali incorporati per i nomi dei membri /nullable/tuple di nint, è più simile a System.Runtime.CompilerServices.ExtensionAttribute, che è presente nel framework sin da quando sono stati introdotti i metodi di estensione.

Conclusione: Inseriremo entrambi gli attributi nel BCL.

Avviso e errore

Non impostare un membro obbligatorio dovrebbe essere un avviso o un errore? È certamente possibile ingannare il sistema, tramite Activator.CreateInstance(typeof(C)) o simili, il che significa che potrebbe non essere in grado di garantire completamente che tutte le proprietà siano sempre impostate. È inoltre consentita l'eliminazione della diagnostica nel sito del costruttore usando il !, che in genere non è consentito per gli errori. Tuttavia, la funzionalità è simile ai campi di sola lettura o alle proprietà init. Si verifica un errore grave se gli utenti tentano di impostare tale membro dopo l'inizializzazione, ma possono essere aggirati tramite reflection.

Conclusione: errori.