Nota
L'accesso a questa pagina richiede l'autorizzazione. È possibile provare ad accedere o modificare le directory.
L'accesso a questa pagina richiede l'autorizzazione. È possibile provare a modificare le directory.
Nel corso della sua storia, .NET ha tentato di mantenere un elevato livello di compatibilità tra le versioni e anche tra le diverse implementazioni di .NET. Anche se .NET 5 (e .NET Core) e versioni successive possono essere considerate come una nuova tecnologia rispetto a .NET Framework, due fattori principali limitano la capacità di questa implementazione di .NET di divergere da .NET Framework:
- Un gran numero di sviluppatori ha originariamente sviluppato o continua a sviluppare applicazioni .NET Framework. Si prevede un comportamento coerente tra le implementazioni di .NET.
- I progetti di libreria .NET Standard consentono agli sviluppatori di creare librerie destinate a API comuni condivise da .NET Framework e .NET 5 (e .NET Core) e versioni successive. Gli sviluppatori prevedono che una libreria usata in un'applicazione .NET si comporti in modo identico alla stessa libreria usata in un'applicazione .NET Framework.
Oltre alla compatibilità tra implementazioni .NET, gli sviluppatori prevedono un elevato livello di compatibilità tra le versioni di una determinata implementazione di .NET. In particolare, il codice scritto per una versione precedente di .NET Core deve essere eseguito senza problemi in .NET 5 o versione successiva. Molti sviluppatori prevedono infatti che le nuove API trovate nelle nuove versioni rilasciate di .NET siano compatibili anche con le versioni non definitive in cui sono state introdotte tali API.
Questo articolo illustra le modifiche che influiscono sulla compatibilità e sul modo in cui il team .NET valuta ogni tipo di modifica. Comprendere come il team .NET si avvicina alle possibili modifiche di rilievo è particolarmente utile per gli sviluppatori che aprono richieste pull che modificano il comportamento delle API .NET esistenti.
Le sezioni seguenti descrivono le categorie di modifiche apportate alle API .NET e il relativo impatto sulla compatibilità delle applicazioni. Le modifiche sono consentite (✔️), non consentite (❌) o richiedono un giudizio e una valutazione del comportamento prevedibile, ovvio e coerente del comportamento precedente (❓).
Annotazioni
- Oltre a fungere da guida alla valutazione delle modifiche apportate alle librerie .NET, gli sviluppatori di librerie possono anche usare questi criteri per valutare le modifiche alle librerie destinate a più implementazioni e versioni .NET.
- Per informazioni sulle categorie di compatibilità, ad esempio compatibilità avanti e indietro, vedere Come le modifiche al codice possono influire sulla compatibilità.
Modifiche al contratto pubblico
Le modifiche apportate a questa categoria modificano l'area della superficie pubblica di un tipo. La maggior parte delle modifiche apportate a questa categoria non è consentita perché violano la compatibilità con le versioni precedenti (la possibilità di un'applicazione sviluppata con una versione precedente di un'API da eseguire senza ricompilare in una versione successiva).
Tipi
✔️ ALLOWED: rimozione di un'implementazione dell'interfaccia da un tipo quando l'interfaccia è già implementata da un tipo di base
❓ RICHIEDE GIUDIZIO: Aggiunta di una nuova implementazione dell'interfaccia a un tipo
Si tratta di una modifica accettabile perché non influisce negativamente sui client esistenti. Tutte le modifiche apportate al tipo devono funzionare entro i limiti delle modifiche accettabili definite qui affinché la nuova implementazione rimanga accettabile. È necessaria estrema cautela quando si aggiungono interfacce che influiscono direttamente sulla capacità di un designer o di un serializzatore di generare codice o dati che non possono essere consumati a livelli inferiori. Un esempio è l'interfaccia ISerializable .
❓ RICHIEDE GIUDIZIO: Introduzione di una nuova classe base
Un tipo può essere introdotto in una gerarchia tra due tipi esistenti se non introduce nuovi membri astratti o modifica la semantica o il comportamento dei tipi esistenti. Ad esempio, in .NET Framework 2.0, la DbConnection classe è diventata una nuova classe base per SqlConnection, che in precedenza era derivata direttamente da Component.
✔️ CONSENTITO: Spostare un tipo da un'assemblea a un'altra
L'assembly vecchio deve essere contrassegnato con TypeForwardedToAttribute che punta al nuovo assembly.
✔️ CONSENTITO: cambiamento di un tipo struct in un
readonly structtipoLa modifica di un
readonly structtipo in unstructtipo non è consentita.✔️ ALLOWED: aggiunta della parola chiave sealed o abstract a un tipo quando non sono presenti costruttori accessibili (pubblici o protetti)
✔️ ALLOWED: espansione della visibilità di un tipo
❌ DISALLOWED: modifica dello spazio dei nomi o del nome di un tipo
❌ DISALLOWED: ridenominazione o rimozione di un tipo pubblico
In questo modo viene interrotto tutto il codice che usa il tipo rinominato o rimosso.
Annotazioni
In rari casi, .NET può rimuovere un'API pubblica. Per altre informazioni, vedere Rimozione di API in .NET. Per informazioni sui criteri di supporto di .NET, vedere Criteri di supporto di .NET.
❌ DISALLOWED: modifica del tipo sottostante di un'enumerazione
Si tratta di una modifica in fase di compilazione e di una modifica di rottura comportamentale, nonché di una modifica binaria che può rendere gli argomenti di attributo non analizzabili.
❌ DISALLOWED: sigillare un tipo che era precedentemente non sigillato
❌ NON CONSENTITO: aggiungere un'interfaccia al set dei tipi base di un'interfaccia
Se un'interfaccia implementa un'interfaccia non implementata in precedenza, tutti i tipi che avevano implementato la versione originale dell'interfaccia vengono interrotti.
❓ RICHIEDE GIUDIZIO: Rimozione di una classe dal set di classi di base o un'interfaccia dal set di interfacce implementate
Esiste un'eccezione alla regola per la rimozione dell'interfaccia: è possibile aggiungere l'implementazione di un'interfaccia che deriva dall'interfaccia rimossa. Ad esempio, è possibile rimuovere IDisposable se il tipo o l'interfaccia ora implementa IComponent, che implementa IDisposable.
❌ DISALLOWED: modifica di un
readonly structtipo in un tipo structLa modifica di un
structtipo a unreadonly structtipo è tuttavia consentita.❌ NON CONSENTITO: il cambiamento di un tipo struct in un tipo
ref struct, e viceversa❌ DISALLOWED: riduzione della visibilità di un tipo
Tuttavia, è consentito aumentare la visibilità di un tipo.
Membri
✔️ CONSENTITO: espansione della visibilità di un membro che non è virtuale
✔️ ALLOWED: aggiunta di un membro astratto a un tipo pubblico senza costruttori accessibili (pubblici o protetti) oppure il tipo è sigillato
Tuttavia, l'aggiunta di un membro astratto a un tipo con costruttori accessibili (pubblici o protetti) e non
sealednon è permessa.✔️ CONCESSO: Restrizione della visibilità di un membro protetto quando il tipo non dispone di costruttori accessibili (pubblici o protetti) o il tipo è sigillato
✔️ CONSENTITO: Spostare un membro in una classe superiore nella gerarchia rispetto a quella da cui è stato rimosso
✔️ ALLOWED: aggiunta o rimozione di un override
L'introduzione di un override potrebbe far sì che i consumer precedenti ignorino l'override quando chiamano la base.
✔️ ALLOWED: aggiunta di un costruttore a una classe, insieme a un costruttore senza parametri se la classe in precedenza non aveva costruttori
Tuttavia, l'aggiunta di un costruttore a una classe che in precedenza non aveva costruttori senza aggiungere il costruttore senza parametri non è consentita.
✔️ CONSENTITO: passaggio da un valore di ritorno
ref readonlyaref(ad eccezione di metodi virtuali o interfacce)✔️ ALLOWED: Chiamata di un nuovo evento non definito in precedenza
❓ RICHIEDE UNA VALUTAZIONE: Aggiungere un nuovo campo di istanza a un tipo
Questa modifica influisce sulla serializzazione.
❌ DISALLOWED: ridenominazione o rimozione di un membro o di un parametro pubblico
In questo modo viene interrotto tutto il codice che usa il membro o il parametro rinominato o rimosso.
Ciò include la rimozione o la ridenominazione di un getter o un setter da una proprietà, così come la ridenominazione o la rimozione dei membri dell'enumerazione.
❓ RICHIEDE GIUDIZIO: Aggiunta di un membro a un'interfaccia
Anche se si tratta di un cambiamento significativo in quanto aumenta la versione minima di .NET a .NET Core 3.0 (C# 8.0), quando sono stati introdotti membri di interfaccia di default, è consentita l'aggiunta di un membro statico, non astratto e non virtuale a un'interfaccia.
Se si specifica un'implementazione, l'aggiunta di un nuovo membro a un'interfaccia esistente non comporta necessariamente errori di compilazione negli assembly downstream. Tuttavia, non tutte le lingue supportano le macchine virtuali di distribuzione. In alcuni scenari, inoltre, il runtime non può decidere quale membro dell'interfaccia predefinito richiamare. In alcuni scenari, le interfacce vengono implementate dai
ref structtipi. Poiché i tipiref structnon possono essere incapsulati, non possono essere convertiti in tipi di interfaccia. Pertanto,ref structi tipi devono fornire un'implementazione implicita per ogni membro dell'interfaccia. Non possono usare l'implementazione predefinita fornita dall'interfaccia . Per questi motivi, usare il giudizio quando si aggiunge un membro a un'interfaccia esistente.❌ DISALLOWED: modifica del valore di una costante o di un membro di enumerazione pubblico
❌ DISALLOWED: modifica del tipo di una proprietà, di un campo, di un parametro o di un valore restituito
❌ DISALLOWED: aggiunta, rimozione o modifica dell'ordine dei parametri
❌ DISALLOWED: aggiunta o rimozione della parola chiave in, out o ref da un parametro
❌ DISALLOWED: ridenominazione di un parametro (inclusa la modifica del relativo caso)
Questo è considerato un'interruzione per due motivi:
Interrompe scenari ad associazione tardiva, ad esempio la funzionalità di associazione tardiva in Visual Basic e dinamica in C#.
Interrompe la compatibilità del codice sorgente quando gli sviluppatori usano argomenti con nome.
❌ DISALLOWED: passaggio da un
refvalore restituito a unref readonlyvalore restituito❌️ NON CONSENTITO: Cambio da valore restituito
ref readonlyarefin un metodo virtuale o interfaccia❌ DISALLOWED: aggiunta o rimozione di abstract da un membro
❌ DISALLOWED: Rimozione della parola chiave virtuale da un membro
❌ DISALLOWED: aggiungere il termine chiave virtual a un membro
Anche se spesso non si tratta di una modifica che causa un'interruzione perché il compilatore C# tende a generare istruzioni callvirt Intermediate Language (IL) per chiamare metodi non virtuali (
callvirtesegue un controllo Null, mentre una chiamata normale non lo è), questo comportamento non è invariabile per diversi motivi:- C# non è l'unico linguaggio destinato a .NET.
- Il compilatore C# tenta sempre di più di ottimizzare
callvirtuna chiamata normale ogni volta che il metodo di destinazione non è virtuale e probabilmente non è Null, ad esempio un metodo a cui si accede tramite l'operatore di propagazione null ?.
Rendere un metodo virtuale significa che il codice utente spesso finisce per chiamarlo non virtualmente.
❌ DISALLOWED: Creazione di un membro virtuale astratta
Un membro virtuale fornisce un'implementazione del metodo che può essere sottoposta a override da una classe derivata. Un membro astratto non fornisce alcuna implementazione e deve essere sovrascritto.
❌ NON CONSENTITO: aggiungere la parola chiave sealed a un membro dell'interfaccia
L'aggiunta
sealeda un membro di interfaccia predefinita lo rende non virtuale, impedendo la chiamata dell'implementazione di un tipo derivato di tale membro.❌ DISALLOWED: aggiunta di un membro astratto a un tipo pubblico che ha costruttori accessibili (pubblici o protetti) e che non è sigillato
❌ DISALLOWED: aggiunta o rimozione della parola chiave static da un membro
❌ DISALLOWED: aggiunta di un overload che esclude un overload esistente e definisce un comportamento diverso
Ciò interrompe i client esistenti vincolati al sovraccarico precedente. Ad esempio, se una classe ha una singola versione di un metodo che accetta un oggetto UInt32, un cliente esistente verrà correttamente associato a tale overload quando si passa un valore Int32. Tuttavia, se si aggiunge un overload che accetta un Int32, quando si ricompila o si usa l'associazione tardiva, il compilatore ora si associa al nuovo overload. Se si verifica un comportamento diverso, si tratta di una modifica rompente.
❌ DISALLOWED: aggiungere un costruttore a una classe che in precedenza non aveva alcun costruttore senza includere il costruttore senza parametri
❌️ NON CONSENTITO: Impostazione a sola lettura per un campo
❌ NON CONSENTITO: Ridurre la visibilità di un membro
Ciò include la riduzione della visibilità di un membro protetto quando ci sono costruttori accessibili (
publicoprotected) e il tipo non è sigillato. In caso contrario, è consentita la riduzione della visibilità di un membro protetto.È consentito aumentare la visibilità di un membro.
❌ DISALLOWED: modifica del tipo di membro
Impossibile modificare il valore restituito di un metodo o del tipo di una proprietà o di un campo. Ad esempio, la firma di un metodo che restituisce un oggetto Object non può essere modificata per restituire un Stringoggetto o viceversa.
❌ NON CONSENTITO: aggiungere un campo di istanza a una struttura senza campi privati
Se uno struct ha solo campi pubblici o non dispone di campi, i chiamanti possono dichiarare variabili locali di tale tipo di struct senza chiamare il costruttore dello struct o inizializzare prima di tutto l'oggetto locale in
default(T), purché tutti i campi pubblici vengano impostati nello struct prima di usarlo. L'aggiunta di nuovi campi, pubblico o non pubblico, a tale struct è una modifica che causa un'interruzione di origine per questi chiamanti, perché il compilatore richiederà ora l'inizializzazione dei campi aggiuntivi.Inoltre, l'aggiunta di nuovi campi pubblici o non pubblici a uno struct senza campi oppure con solo campi pubblici rappresenta una modifica binaria sostanziale per i chiamanti che hanno applicato
[SkipLocalsInit]al loro codice. Poiché il compilatore non è a conoscenza di questi campi in fase di compilazione, potrebbe generare IL che non inizializza completamente lo struct, causando la creazione dello struct da dati dello stack non inizializzati.Se uno struct include campi non pubblici, il compilatore applica già l'inizializzazione tramite il costruttore o
default(T)e l'aggiunta di nuovi campi di istanza non è una modifica che causa un'interruzione.❌ DISALLOWED: attivare un evento esistente quando non è mai stato attivato prima
Modifiche comportamentali
Assemblaggi
✔️ CONSENTITO: rendere un assembly portabile quando le stesse piattaforme sono ancora supportate
❌ DISALLOWED: modifica del nome di un assembly
❌ DISALLOWED: modifica della chiave pubblica di un assembly
Proprietà, campi, parametri e valori restituiti
✔️ ALLOWED: modifica del valore di una proprietà, di un campo, di un valore restituito o di un parametro out in un tipo più derivato
Ad esempio, un metodo che restituisce un tipo di Object può restituire un'istanza di String. La firma del metodo non può tuttavia cambiare.
✔️ CONSENTITO: aumento dell'intervallo di valori accettati per una proprietà o un parametro se il membro non è virtuale
Anche se l'intervallo di valori che possono essere passati al metodo o restituiti dal membro può espandersi, il parametro o il tipo di membro non può. Ad esempio, mentre i valori passati a un metodo possono espandersi da 0-124 a 0-255, il tipo di parametro non può passare da Byte a Int32.
❌ DISALLOWED: aumento dell'intervallo di valori accettati per una proprietà o un parametro se il membro è virtuale
Questa modifica interrompe i membri sottoposti a override esistenti, che non funzioneranno correttamente per l'intervallo esteso di valori.
❌ DISALLOWED: riduzione dell'intervallo di valori accettati per una proprietà o un parametro
❌ NON CONSENTITO: incremento dell'intervallo di valori restituiti per una proprietà, un campo, un valore restituito o un parametro "out"
❌ DISALLOWED: modifica dei valori restituiti per una proprietà, un campo, un valore restituito del metodo o un parametro out
❌ DISALLOWED: modifica del valore predefinito di una proprietà, di un campo o di un parametro
La modifica o la rimozione di un valore predefinito di un parametro non è un'interruzione binaria. La rimozione di un valore predefinito di un parametro è un'interruzione di origine e la modifica di un valore predefinito di un parametro può comportare un'interruzione comportamentale dopo la ricompilazione.
Per questo motivo, la rimozione dei valori predefiniti dei parametri è accettabile nel caso specifico di "spostamento" di tali valori predefiniti in un nuovo overload del metodo per eliminare l'ambiguità. Si consideri ad esempio un metodo
MyMethod(int a = 1)esistente. Se si introduce un overload diMyMethodcon due parametri facoltativiaeb, è possibile mantenere la compatibilità spostando il valore predefinito dianel nuovo overload. Ora i due sovraccarichi sonoMyMethod(int a)eMyMethod(int a = 1, int b = 2). Questo modello consente la compilazione diMyMethod().❌ DISALLOWED: modifica della precisione di un valore restituito numerico
❓ RICHIEDE GIUDIZIO: modifica nell'analisi dell'input e generazione di nuove eccezioni (anche se il comportamento di analisi non è specificato nella documentazione
Eccezioni
✔️ ALLOWED: lancio di un'eccezione più specifica rispetto a un'eccezione già esistente
Poiché la nuova eccezione è una sottoclasse di un'eccezione esistente, il codice di gestione delle eccezioni precedente continua a gestire l'eccezione. Ad esempio, in .NET Framework 4, i metodi per la creazione e il recupero della cultura hanno iniziato a generare una CultureNotFoundException invece di una ArgumentException se la cultura non poteva essere trovata. Poiché CultureNotFoundException deriva da ArgumentException, si tratta di una modifica accettabile.
✔️ ALLOWED: lanciare un'eccezione più specifica di NotSupportedException, NotImplementedException, NullReferenceException
✔️ ALLOWED: Lancio di un'eccezione considerata non recuperabile
Le eccezioni irreversibili non devono essere rilevate, ma devono essere gestite da un gestore catch-all di alto livello. Pertanto, gli utenti non devono avere codice che intercetta queste eccezioni esplicite. Le eccezioni irreversibili sono:
✔️ CONSENTITO: Lanciare una nuova eccezione in un nuovo percorso di codice
L'eccezione deve essere applicata solo a un nuovo percorso di codice eseguito con nuovi valori di parametro o stato e che non può essere eseguito dal codice esistente destinato alla versione precedente.
✔️ ALLOWED: rimozione di un'eccezione per abilitare un comportamento più affidabile o nuovi scenari
Ad esempio, un
Dividemetodo che in precedenza gestiva solo valori positivi e generava un oggetto ArgumentOutOfRangeException altrimenti può essere modificato in modo da supportare valori negativi e positivi senza generare un'eccezione.✔️ CONSENTITO: Modifica del testo di un messaggio di errore
Gli sviluppatori non devono basarsi sul testo dei messaggi di errore, che cambiano anche in base alle impostazioni cultura dell'utente.
❌ DISALLOWED: generazione di un'eccezione in qualsiasi altro caso non elencato in precedenza
❌ DISALLOWED: Rimozione di un'eccezione in qualsiasi altro caso non elencato in precedenza
Attributi
✔️ ALLOWED: modifica del valore di un attributo che non è osservabile
❌ NON PERMESSO: modificare il valore di un attributo osservabile
❓ RICHIEDE GIUDIZIO: Rimozione di un attributo
Nella maggior parte dei casi, la rimozione di un attributo (ad esempio NonSerializedAttribute) è una modifica che interrompe la compatibilità.
Supporto delle piattaforme
✔️ ALLOWED: supporto di un'operazione su una piattaforma non supportata in precedenza
❌ DISALLOWED: non supporta o richiede un Service Pack specifico per un'operazione che in precedenza era supportata su una piattaforma
Modifiche all'implementazione interna
❓ RICHIEDE GIUDIZIO: Modifica dell'area di superficie di un tipo interno
Tali modifiche sono generalmente consentite, sebbene interrompano la riflessione privata. In alcuni casi, in cui le librerie di terze parti più diffuse o un numero elevato di sviluppatori dipendono dalle API interne, tali modifiche potrebbero non essere consentite.
❓ RICHIEDE GIUDIZIO: Modifica dell'implementazione interna di un membro
Queste modifiche sono in genere consentite, anche se interrompono la riflessione privata. In alcuni casi, in cui il codice del cliente dipende spesso dalla reflection privata o quando la modifica introduce effetti collaterali indesiderati, queste modifiche potrebbero non essere consentite.
✔️ ALLOWED: miglioramento delle prestazioni di un'operazione
La possibilità di modificare le prestazioni di un'operazione è essenziale, ma tali modifiche possono interrompere il codice che si basa sulla velocità corrente di un'operazione. Ciò è particolarmente vero del codice che dipende dalla tempistica delle operazioni asincrone. La modifica delle prestazioni non deve avere alcun effetto sugli altri comportamenti dell'API in questione; in caso contrario, si verificherà un'interruzione.
✔️ CONSENTITO: Cambiamento indiretto (e spesso negativo) delle prestazioni di un'operazione
Se la modifica in questione non è categorizzata come un'interruzione per un altro motivo, questo è accettabile. Spesso è necessario eseguire azioni che possono includere operazioni aggiuntive o che aggiungono nuove funzionalità. Ciò influirà quasi sempre sulle prestazioni, ma potrebbe essere essenziale per rendere l'API in questione come previsto.
❌ DISALLOWED: modifica di un'API sincrona in asincrona (e viceversa)
Modifiche al codice
✔️ ALLOWED: aggiunta di parametri a un parametro
❌ DISALLOWED: aggiunta della dichiarazione checked a un blocco di codice
Questa modifica potrebbe causare la generazione di un OverflowException codice eseguito in precedenza e non è accettabile.
❌ DISALLOWED: rimozione di parametri da un parametro
❌ DISALLOWED: modifica dell'ordine in cui vengono attivati gli eventi
Gli sviluppatori possono ragionevolmente aspettarsi che gli eventi vengano generati nello stesso ordine e il codice dello sviluppatore dipende spesso dall'ordine in cui vengono generati gli eventi.
❌ DISALLOWED: rimozione della generazione di un evento in una determinata azione
❌ DISALLOWED: modifica del numero di volte in cui vengono chiamati gli eventi specificati
❌ DISALLOWED: aggiunta di FlagsAttribute a un tipo di enumerazione