Membri dell'istanza di Readonly
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
Puoi scoprire di più sul processo di adozione delle speclet di funzionalità nello standard del linguaggio C# nell'articolo che discute le specifiche di e.
Problema legato al campione: <https://github.com/dotnet/csharplang/issues/1710>
Sommario
Fornire un modo per specificare che singoli membri di istanza in uno struct non modificano lo stato, nello stesso modo in cui readonly struct
specifica che nessun membro di istanza modifica lo stato.
Vale la pena notare che readonly membro di istanza != membro di istanza pura. Un membro dell'istanza di puro garantisce che non verrà modificato alcuno stato. Un membro dell'istanza di readonly
garantisce solo che lo stato dell'istanza non verrà modificato.
Tutti i membri dell'istanza di un readonly struct
possono essere considerati in modo implicito membri dell'istanza readonly membri espliciti dell'istanza dichiarati in struct non readonly si comportano nello stesso modo. Ad esempio, creerebbero comunque copie nascoste se si chiamasse un membro dell'istanza (nell'istanza corrente o in un campo dell'istanza) che di per sé non è di sola lettura.
Motivazione
Prima di C# 8.0, gli utenti hanno la possibilità di creare readonly struct
tipi che il compilatore impone che tutti i campi siano di sola lettura (e per estensione, che nessun membro dell'istanza modifichi lo stato). Esistono tuttavia alcuni scenari in cui si dispone di un'API esistente che espone campi accessibili o che include una combinazione di membri modificanti e non modificabili. In queste circostanze, non è possibile contrassegnare il tipo come readonly
(sarebbe una modifica incompatibile).
Questo in genere non ha un impatto significativo, tranne nel caso di parametri in
. Con i parametri in
per gli struct non di sola lettura, il compilatore eseguirà una copia del parametro per ogni invocazione del membro di istanza, poiché non può garantire che l'invocazione non modifichi lo stato interno. Ciò può portare a una moltitudine di copie e prestazioni complessive peggiori rispetto a se si fosse appena passato lo struct direttamente in base al valore. Per un esempio, vedere questo codice in sharplab
Altri scenari in cui possono verificarsi copie nascoste includono static readonly
campi e valori letterali . Se in futuro sono supportate, costanti blttable finirebbero nella stessa barca; ovvero richiedono attualmente una copia completa (nella chiamata del membro dell'istanza) se lo struct non è contrassegnato readonly
.
Disegno
Consentire a un utente di specificare che un membro dell'istanza è, da solo, readonly
e non modifica lo stato dell'istanza (con tutte le verifiche appropriate eseguite dal compilatore, naturalmente). Per esempio:
public struct Vector2
{
public float x;
public float y;
public readonly float GetLengthReadonly()
{
return MathF.Sqrt(LengthSquared);
}
public float GetLength()
{
return MathF.Sqrt(LengthSquared);
}
public readonly float GetLengthIllegal()
{
var tmp = MathF.Sqrt(LengthSquared);
x = tmp; // Compiler error, cannot write x
y = tmp; // Compiler error, cannot write y
return tmp;
}
public readonly float LengthSquared
{
get
{
return (x * x) +
(y * y);
}
}
}
public static class MyClass
{
public static float ExistingBehavior(in Vector2 vector)
{
// This code causes a hidden copy, the compiler effectively emits:
// var tmpVector = vector;
// return tmpVector.GetLength();
//
// This is done because the compiler doesn't know that `GetLength()`
// won't mutate `vector`.
return vector.GetLength();
}
public static float ReadonlyBehavior(in Vector2 vector)
{
// This code is emitted exactly as listed. There are no hidden
// copies as the `readonly` modifier indicates that the method
// won't mutate `vector`.
return vector.GetLengthReadonly();
}
}
Readonly può essere applicato alle funzioni di accesso alle proprietà per indicare che this
non verrà modificato nella funzione di accesso. Negli esempi seguenti sono presenti setter di sola lettura perché tali funzioni di accesso modificano lo stato del campo membro, ma non modificano il valore di tale campo membro.
public readonly int Prop1
{
get
{
return this._store["Prop1"];
}
set
{
this._store["Prop1"] = value;
}
}
Quando readonly
viene applicato alla sintassi della proprietà, significa che tutti gli accessori sono readonly
.
public readonly int Prop2
{
get
{
return this._store["Prop2"];
}
set
{
this._store["Prop2"] = value;
}
}
Readonly può essere applicato solo alle funzioni di accesso che non modificano il tipo contenitore.
public int Prop3
{
readonly get
{
return this._prop3;
}
set
{
this._prop3 = value;
}
}
Readonly può essere applicato ad alcune proprietà implementate automaticamente, ma non avrà un effetto significativo. Il compilatore considererà tutti i getter implementati automaticamente come di sola lettura indipendentemente dalla presenza della parola chiave readonly
.
// Allowed
public readonly int Prop4 { get; }
public int Prop5 { readonly get; set; }
// Not allowed
public int Prop6 { readonly get; }
public readonly int Prop7 { get; set; }
public int Prop8 { get; readonly set; }
Readonly può essere applicato agli eventi implementati manualmente, ma non agli eventi di tipo campo. Non è possibile applicare readonly alle singole funzioni di accesso agli eventi (aggiunta/rimozione).
// Allowed
public readonly event Action<EventArgs> Event1
{
add { }
remove { }
}
// Not allowed
public readonly event Action<EventArgs> Event2;
public event Action<EventArgs> Event3
{
readonly add { }
readonly remove { }
}
public static readonly event Event4
{
add { }
remove { }
}
Altri esempi di sintassi:
- Membri con corpo di espressione:
public readonly float ExpressionBodiedMember => (x * x) + (y * y);
- Vincoli generici:
public readonly void GenericMethod<T>(T value) where T : struct { }
Il compilatore genera il membro dell'istanza, come di consueto, e genera inoltre un attributo riconosciuto dal compilatore che indica che il membro dell'istanza non modifica lo stato. In questo modo, il parametro this
nascosto diventa in T
anziché ref T
.
In questo modo l'utente può chiamare in modo sicuro il metodo di istanza senza che il compilatore debba eseguire una copia.
Le restrizioni includono:
- Il modificatore
readonly
non può essere applicato a metodi statici, costruttori o distruttori. - Il modificatore
readonly
non può essere applicato ai delegati. - Il modificatore
readonly
non può essere applicato ai membri della classe o dell'interfaccia.
Svantaggi
Gli stessi svantaggi esistenti con i metodi readonly struct
di oggi. Alcuni codici possono comunque causare copie nascoste.
Note
È anche possibile usare un attributo o un'altra parola chiave.
Questa proposta è in qualche modo correlata (ma è più un sottoinsieme di) functional purity
e/o constant expressions
, che hanno avuto entrambe alcune proposte esistenti.
C# feature specifications