Obligatoriska medlemmar
Note
Den här artikeln är en funktionsspecifikation. Specifikationen fungerar som designdokument för funktionen. Den innehåller föreslagna specifikationsändringar, tillsammans med information som behövs under utformningen och utvecklingen av funktionen. Dessa artiklar publiceras tills de föreslagna specifikationsändringarna har slutförts och införlivats i den aktuella ECMA-specifikationen.
Det kan finnas vissa skillnader mellan funktionsspecifikationen och den slutförda implementeringen. Dessa skillnader fångas i de relevanta anteckningarna från -språkkonstruktionsmötet (Language Design Meeting).
Du kan läsa mer om processen för att införa funktionsspecifikationer i C#-språkstandarden i artikeln om specifikationerna.
Champion-fråga: https://github.com/dotnet/csharplang/issues/3630
Sammanfattning
Det här förslaget lägger till ett sätt att ange att en egenskap eller ett fält måste anges under objektinitiering, vilket tvingar instansens skapare att ange ett initialt värde för medlemmen i en objektinitierare på skapandeplatsen.
Motivation
Objekthierarkier kräver idag mycket standardkod för att kunna överföra data över alla nivåer i hierarkin. Nu ska vi titta på en enkel hierarki med en Person
som kan definieras i 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;
}
}
Det pågår massor av upprepningar här:
- Vid roten av hierarkin måste typen av varje egenskap upprepas två gånger och namnet måste upprepas fyra gånger.
- På den härledda nivån måste typen av varje ärvd egenskap upprepas en gång och namnet måste upprepas två gånger.
Detta är en enkel hierarki med 3 egenskaper och 1 arvsnivå, men många verkliga exempel på dessa typer av hierarkier går många nivåer djupare och ackumulerar större och större antal egenskaper för att gå vidare när de gör det. Roslyn är en sådan kodbas, till exempel i de olika trädtyperna som gör våra CST och AST. Den här kapslingen är så omständlig att vi har kodgeneratorer för att generera konstruktorer och definitioner av dessa typer, och många kunder använder liknande metoder för problemet. C# 9 introducerar rekord, vilket för vissa scenarier kan förbättra detta:
record Person(string FirstName, string LastName, string MiddleName = "");
record Student(int ID, string FirstName, string LastName, string MiddleName = "") : Person(FirstName, LastName, MiddleName);
record
eliminerar den första dupliceringskällan, men den andra dupliceringskällan förblir oförändrad: tyvärr är detta källan till duplicering som växer när hierarkin växer och är den mest smärtsamma delen av dupliceringen att åtgärda efter att ha gjort en ändring i hierarkin eftersom den krävde att jaga hierarkin genom alla dess platser, till och med mellan projekt och potentiellt icke-bakåtkompatibla konsumenter.
Som en lösning för att undvika den här dupliceringen har vi länge sett konsumenter som använder objektinitierare som ett sätt att undvika att skriva konstruktorer. Före C# 9 hade detta dock 2 stora nackdelar:
- Objekthierarkin måste vara helt föränderlig, med
set
-åtkomst på varje egenskap. - Det finns inget sätt att se till att varje instansiering av ett objekt från grafen anger varje medlem.
C# 9 tog återigen upp det första problemet här genom att introducera init
-accessorn: med det kan dessa egenskaper anges vid skapande/initiering av objekt, men inte senare. Men vi har fortfarande det andra problemet: egenskaperna i C# har varit valfria sedan C# 1.0. Nullbara referenstyper, som introducerades i C# 8.0, åtgärdade en del av det här problemet: om en konstruktor inte initierar en icke-nullbar referenstypsegenskap varnas användaren om det. Detta löser dock inte problemet: här vill användaren inte upprepa stora delar av sin typ i konstruktorn, de vill skicka vidare -krav för att ställa in egenskaper hos sina konsumenter. Det ger inte heller några varningar om ID
från Student
, eftersom det är en värdetyp. Dessa scenarier är mycket vanliga i databasmodell-ORM:er, till exempel EF Core, som måste ha en offentlig parameterlös konstruktor men sedan driva nullabiliteten för raderna baserat på egenskapernas nullbarhet.
Det här förslaget syftar till att åtgärda dessa problem genom att införa en ny funktion för C#: obligatoriska medlemmar. Obligatoriska medlemmar måste initieras av konsumenter, snarare än av typförfattaren, med olika anpassningar för att möjliggöra flexibilitet för flera konstruktorer och andra scenarier.
Detaljerad design
class
, struct
och record
typer får möjlighet att deklarera en required_member_list. Den här listan innehåller alla egenskaper och fält av en typ som anses nödvändigaoch som måste initieras under konstruktionen och initialiseringen av en instans av typen. Typer ärver dessa listor från sina basklasser automatiskt, vilket ger en sömlös upplevelse som eliminerar standardkod och repetitiv kod.
required
modifierare
Vi lägger till 'required'
i listan över modifierare i field_modifier och property_modifier.
required_member_list av en typ består av alla medlemmar som har required
tillämpat på dem. Därför ser den Person
typen från tidigare nu ut så här:
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; }
}
Alla konstruktorer av en typ som har en required_member_list uttrycker automatiskt ett kontrakt som innebär att användare av typen måste initiera alla egenskaper i listan. Det är ett fel för en konstruktor att annonsera ett kontrakt som kräver en medlem som inte är minst lika tillgänglig som konstruktorn själv. Till exempel:
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
är endast giltigt i typerna class
, struct
och record
. Det är inte giltigt för interface
-typer.
required
kan inte kombineras med följande modifierare:
fixed
ref readonly
ref
const
static
required
tillåts inte tillämpas på indexerare.
Kompilatorn utfärdar en varning när Obsolete
tillämpas på en obligatorisk medlem av en typ och:
- Typen är inte markerad som
Obsolete
eller - Konstruktörer som inte har attributet
SetsRequiredMembersAttribute
är inte markerade medObsolete
.
SetsRequiredMembersAttribute
Alla konstruktorer i en typ med nödvändiga medlemmar, eller vars bastyp specificerar nödvändiga medlemmar, måste ha dessa medlemmar inställda av användaren när konstruktorn anropas. För att undanta konstruktorer från detta krav kan en konstruktor hänföras till SetsRequiredMembersAttribute
, vilket tar bort dessa krav. Konstruktorns brödtext verifieras inte för att säkerställa att den definitivt anger de nödvändiga medlemmarna av typen.
SetsRequiredMembersAttribute
tar bort alla krav från en konstruktor, och dessa krav kontrolleras inte överhuvudtaget för giltighet. Obs! Detta är utvägen om det är nödvändigt att ärva från en typ med en ogiltig obligatorisk medlemslista: markera konstruktorn för den typen med SetsRequiredMembersAttribute
, och inga fel kommer att rapporteras.
Om en konstruktor C
kedjar vidare till en base
- eller this
-konstruktor som är markerad med SetsRequiredMembersAttribute
, måste C
också markeras med SetsRequiredMembersAttribute
.
För posttyper kommer vi att generera SetsRequiredMembersAttribute
på den syntetiserade kopieringskonstruktorn för en post om posttypen eller någon av dess bastyper har obligatoriska medlemmar.
Obs! En tidigare version av det här förslaget hade en större metalanguage kring initiering, vilket gjorde det möjligt att lägga till och ta bort enskilda obligatoriska medlemmar från en konstruktor, samt validering av att konstruktorn angav alla nödvändiga medlemmar. Detta ansågs vara för komplext för den första versionen och togs bort. Vi kan titta på hur du lägger till mer komplexa kontrakt och ändringar som en senare funktion.
Tillämpning
För varje konstruktor Ci
i typen T
med nödvändiga medlemmar R
måste användare som anropar Ci
göra ett av följande:
- Ange alla medlemmar i
R
i en object_initializer på object_creation_expression, - Eller ange alla medlemmar i
R
via avsnittet named_argument_list i en attribute_target.
såvida inte Ci
tillskrivs SetsRequiredMembers
.
Om den aktuella kontexten inte tillåter en object_initializer eller inte är en attribute_target, och Ci
inte tillskrivs SetsRequiredMembers
, är det ett fel att anropa Ci
.
new()
begränsning
En typ med en parameterlös konstruktor som annonserar ett kontrakt inte får ersättas med en typparameter som är begränsad till new()
eftersom det inte finns något sätt för den allmänna instansieringen att säkerställa att kraven uppfylls.
struct
default
s
Obligatoriska medlemmar gäller inte för instanser av struct
-typer som är skapade med default
eller default(StructType)
. De tillämpas för struct
instanser som skapats med new StructType()
, även om StructType
inte har någon parameterlös konstruktor och standardkonstruktionskonstruktorn används.
Tillgänglighet
Det är ett fel att göra en medlem obligatorisk om medlemen inte kan anges i någon kontext där den innehållande typen synliggörs.
- Om medlemmen är ett fält kan det inte vara
readonly
. - Om medlemmen är en egenskap måste den ha en setter eller initer som är minst lika tillgänglig som medlemmens innehållande typ.
Det innebär att följande fall inte tillåts:
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
}
Det är ett fel att dölja en required
medlem, eftersom den medlemmen inte längre kan anges av en konsument.
När du åsidosätter en required
medlem måste nyckelordet required
inkluderas i metodsignaturen. Detta görs så att vi i framtiden, om vi någonsin vill tillåta att en egenskap blir frivillig med en åsidosättning, har vi designutrymme för att göra det.
Överskridningar tillåts markera en medlem required
om det inte var required
i bastypen. En medlem som är så markerad läggs till i listan med obligatoriska medlemmar av den härledda typen.
Typer kan åsidosätta nödvändiga virtuella egenskaper. Det innebär att om den virtuella basegenskapen har lagring och den härledda typen försöker komma åt den grundläggande implementeringen av den egenskapen kan de observera eninitierad lagring. Obs! Detta är ett allmänt C#-antimönster och vi anser inte att det här förslaget bör försöka åtgärda det.
Effekt på nullbar analys
Medlemmar som är markerade required
behöver inte initieras till ett giltigt null-tillstånd i slutet av en konstruktor. Alla required
-medlemmar från den här typen och alla bastyper anses av nullbarhetsanalys vara förvalda i början av vilken konstruktor som helst i den typen, om det inte länkas till en this
- eller base
-konstruktor som tillskrivs SetsRequiredMembersAttribute
.
Nullbar analys varnar för alla required
medlemmar från de aktuella och grundläggande typerna som inte har ett giltigt null-tillstånd i slutet av en konstruktor som tillskrivs 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
}
}
Metadata representation
Följande två attribut är kända för C#-kompilatorn och krävs för att funktionen ska fungera:
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() {}
}
}
Det är ett fel att manuellt tillämpa RequiredMemberAttribute
på en typ.
Varje medlem som är markerad med required
har en RequiredMemberAttribute
tillämpad på sig. Dessutom markeras alla typer som definierar sådana medlemmar med RequiredMemberAttribute
, som en markör som anger att det finns nödvändiga medlemmar i den här typen. Observera att om typen B
härleds från A
och A
definierar required
medlemmar, men B
inte lägger till några nya eller åsidosätter befintliga required
medlemmar, markeras B
inte med en RequiredMemberAttribute
.
Om du vill ta reda på om det finns några obligatoriska medlemmar i B
måste du kontrollera den fullständiga arvshierarkin.
Någon konstruktor i en typ med required
medlemmar som inte har SetsRequiredMembersAttribute
tillämpat på sig markeras med två attribut:
-
System.Runtime.CompilerServices.CompilerFeatureRequiredAttribute
med funktionsnamnet"RequiredMembers"
. -
System.ObsoleteAttribute
med strängen"Types with required members are not supported in this version of your compiler"
och attributet markeras som ett fel för att förhindra att äldre kompilatorer använder dessa konstruktorer.
Vi använder inte en modreq
här eftersom det är ett mål att upprätthålla binär kompatibilitet: om den sista required
egenskapen togs bort från en typ skulle kompilatorn inte längre syntetisera den här modreq
, vilket är en binärbrytande ändring och alla konsumenter skulle behöva kompileras om. En kompilator som förstår required
medlemmar ignorerar det här föråldrade attributet. Observera att medlemmar också kan komma från bastyper: även om det inte finns några nya required
medlemmar i den aktuella typen genereras det här required
attributet om någon bastyp har Obsolete
medlemmar. Om konstruktorn redan har ett Obsolete
-attribut genereras inga ytterligare Obsolete
attribut.
Vi använder både ObsoleteAttribute
och CompilerFeatureRequiredAttribute
eftersom den senare är ny den här versionen och äldre kompilatorer inte förstår den. I framtiden kanske vi kan släppa ObsoleteAttribute
och/eller inte använda den för att skydda nya funktioner, men för tillfället behöver vi båda för fullständigt skydd.
Om du vill skapa en fullständig lista över required
medlemmar R
för en viss typ T
, inklusive alla bastyper, körs följande algoritm:
- För varje
Tb
börjar du medT
och arbetar genom bastypskedjan tillsobject
har nåtts. - Om
Tb
har markerats medRequiredMemberAttribute
samlas alla medlemmar iTb
markerade medRequiredMemberAttribute
in iRb
- För varje
Ri
iRb
, hoppasRi
över om den åsidosätts av någon medlem iR
. - Annars, om någon
Ri
döljs av en medlem iR
, misslyckas sökningen av nödvändiga medlemmar och inga ytterligare åtgärder vidtas. Att anropa någon konstruktor avT
utan attributetSetsRequiredMembers
orsakar ett fel. - Annars läggs
Ri
till iR
.
- För varje
Öppna frågor
Kapslade medlemsinitierare
Vilka kommer tillsynsmekanismerna för kapslade medlemsinitialiseringar att vara? Kommer de inte att tillåtas helt och hållet?
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?
Diskuterade frågor
Tillämpningsnivå för init
-satser
Funktionen init
-satsen implementerades inte i C# 11. Det är fortfarande ett aktivt förslag.
Framtvingar vi strikt att medlemmar som anges i en init
-sats utan initierare måste initiera alla medlemmar? Det verkar troligt att vi gör det, annars skapar vi en enkel felgrop. Men vi riskerar också att återinföra samma problem som vi löst med MemberNotNull
i C# 9. Om vi vill tillämpa detta strikt behöver vi förmodligen ett sätt för en hjälpmetod att visa att den sätter en komponent. Några möjliga syntaxer som vi har diskuterat för detta:
- Tillåt
init
metoder. Dessa metoder kan bara anropas från en konstruktor eller från en annaninit
-metod och kan komma åtthis
som om den finns i konstruktorn (dvs. angereadonly
ochinit
fält/egenskaper). Detta kan kombineras medinit
-satser på sådana metoder. Eninit
-klausul skulle anses vara uppfylld om medlemmen i satsen definitivt tilldelas i brödtexten i metoden/konstruktorn. Att anropa en metod med eninit
-klass som inkluderar en medlem räknas som en tilldelning till den medlemmen. Om vi bestämmer oss för att detta är en väg vi vill följa, nu eller i framtiden, verkar det rimligt att vi inte bör användainit
som nyckelord för init-klausulen i en konstruktor, eftersom det kan skapa förvirring. - Tillåt att
!
-operatorn uttryckligen utelämnar varningen/felet. Om du initierar en medlem på ett komplicerat sätt (till exempel i en delad metod) kan användaren lägga till en!
i init-satsen för att indikera att kompilatorn inte bör söka efter initiering.
Slutsats: Efter diskussion gillar vi idén om !
operatören. Det gör att användaren kan agera medvetet i mer komplicerade scenarier utan att skapa ett stort designhål runt init-metoder och att märka varje metod som att ange medlemmar X eller Y. !
valdes eftersom vi redan använder det för att undertrycka nullbara varningar och att använda det för att berätta för kompilatorn "Jag är smartare än du" på en annan plats är en naturlig förlängning av syntaxformen.
Nödvändiga gränssnittsmedlemmar
Det här förslaget tillåter inte att gränssnitt markerar medlemmar efter behov. Detta skyddar oss från att behöva ta reda på komplexa scenarier kring new()
och gränssnittsbegränsningar i generiska objekt just nu och är direkt relaterade till både fabriker och allmän konstruktion. För att säkerställa att vi har designutrymme i det här området förbjuder vi required
i gränssnitt och förbjuder typer med required_member_lists från att ersättas med typparametrar som är begränsade till new()
. När vi vill ta en bredare titt på allmänna byggscenarier med fabriker kan vi gå tillbaka till det här problemet.
Syntaxfrågor
Funktionen init
-satsen implementerades inte i C# 11. Det är fortfarande ett aktivt förslag.
- Är
init
rätt ord?init
som en postfixmodifierare på konstruktorn kan störa om vi vill återanvända den för fabriker i framtiden och även aktiverainit
-metoder med en prefixmodifierare. Andra möjligheter:set
- Är
required
korrekt modifierare för att ange att alla element initieras? Andra föreslog:default
all
- Med ett utropstecken! för att ange komplex logik
- Ska vi kräva en avgränsare mellan
base
/this
ochinit
?-
:
avgränsare - '', avgränsare
-
- Är
required
rätt modifierare? Andra alternativ som har föreslagits:req
require
mustinit
must
explicit
Slutsats: Vi har tagit bort init
konstruktorsatsen temporärt och vi fortsätter att använda required
som egenskapsmodifierare.
Begränsningar för Init-satser
Funktionen init
-satsen implementerades inte i C# 11. Det är fortfarande ett aktivt förslag.
Ska vi tillåta åtkomst till this
i init-satsen? Om vi vill att tilldelningen i init
ska vara en förkortning för att tilldela medlemmen i själva konstruktorn verkar det som om vi borde göra det.
Skapar den dessutom ett nytt omfång, som base()
gör, eller delar det samma omfång som metodtexten? Detta är särskilt viktigt för saker som lokala funktioner, som init-satsen kanske vill komma åt, eller för namnskuggning, om ett init-uttryck introducerar en variabel via en out
-parameter.
Slutsats: init
-satsen har tagits bort.
Tillgänglighetskrav och init
Funktionen init
-satsen implementerades inte i C# 11. Det är fortfarande ett aktivt förslag.
I versioner av det här förslaget med init
-satsen talade vi om att kunna ha följande scenario:
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
{
}
}
Vi har dock tagit bort init
-klausulen från förslaget just nu, så vi måste besluta om vi ska tillåta detta scenario på ett begränsat sätt. Alternativen vi har är:
- Tillåt inte scenariot. Detta är den mest konservativa metoden, och reglerna i Accessibility är för närvarande skrivna med detta antagande i åtanke. Regeln är att en medlem som krävs måste vara minst lika synlig som sin innehållande typ.
- Kräv att alla konstruktorer antingen är:
- Inte mer synlig än den minst synliga obligatoriska medlemmen.
- Låt
SetsRequiredMembersAttribute
tillämpas på konstruktorn. Dessa skulle se till att alla som kan se en konstruktor antingen kan ange alla de saker som exporteras eller att det inte finns något att ange. Detta kan vara användbart för typer som bara skapas via statiskaCreate
metoder eller liknande byggare, men verktyget verkar totalt sett begränsat.
- Läste ett sätt att ta bort specifika delar av kontraktet till förslaget, enligt beskrivningen i LDM- tidigare.
Slutsats: Alternativ 1, alla nödvändiga medlemmar måste vara minst lika synliga som deras innehållande typ.
Åsidosätt regler
Den nuvarande specifikationen säger att nyckelordet required
måste kopieras över och att åsidosättningar kan göra att en medlem krävs mer, men inte mindre. Är det vad vi vill göra?
Att tillåta borttagning av krav kräver mer kapacitet för kontraktsändring än vad vi för närvarande föreslår.
Slutsats: Att lägga till required
vid åsidosättning är tillåtet. Om den åsidosatta medlemmen är required
måste den överdrivande medlemmen också vara required
.
Alternativ metadatarepresentation
Vi kan också använda en annan metod för att representera metadata och inspireras av koncept från tilläggsmetoder. Vi kan placera en RequiredMemberAttribute
på typen för att ange att typen innehåller nödvändiga medlemmar och sedan lägga en RequiredMemberAttribute
på varje medlem som krävs. Detta skulle förenkla uppslagssekvensen (du behöver inte göra medlemssökning, leta bara efter medlemmar med attributet).
Slutsats: Alternativ godkänd.
Metadata representation
Den metadata-representationen måste godkännas. Vi måste också bestämma om dessa attribut ska ingå i BCL:n.
- För
RequiredMemberAttribute
är det här attributet mer likt de allmänna inbäddade attribut som vi använder för nullable/nint/tuppelns medlemsnamn och tillämpas inte manuellt av användaren i C#. Det är dock möjligt att andra språk vill tillämpa det här attributet manuellt. -
SetsRequiredMembersAttribute
, å andra sidan, används direkt av konsumenterna och bör därför sannolikt finnas i BCL.
Om vi använder den alternativa representationen i föregående avsnitt kan det ändra kalkylen för RequiredMemberAttribute
: i stället för att likna de allmänna inbäddade attributen för nint
/nullable/tuppelns medlemsnamn är det närmare System.Runtime.CompilerServices.ExtensionAttribute
, som har funnits i ramverket sedan tilläggsmetoderna levererades.
Slutsats: Vi placerar båda attributen i BCL:n.
Varning kontra fel
Ska det inte vara en varning eller ett fel att inte ange en obligatorisk medlem? Det är säkert möjligt att lura systemet, via Activator.CreateInstance(typeof(C))
eller liknande, vilket innebär att vi kanske inte helt kan garantera att alla egenskaper alltid är inställda. Vi tillåter även undertryckning av diagnostik på konstruktorplatsen med hjälp av !
, som vi vanligtvis inte tillåter för fel. Funktionen liknar dock skrivskyddade fält eller init-egenskaper, eftersom vi ger ett hårt felmeddelande om användarna försöker ange en sådan medlem efter initieringen, men de kan kringgås genom reflection.
slutsats: Fel.
C# feature specifications