Regels voor compatibiliteit wijzigen
In de loop van de geschiedenis heeft .NET geprobeerd een hoog compatibiliteitsniveau van versie tot versie en implementaties van .NET te handhaven. Hoewel .NET 5 (en .NET Core) en latere versies kunnen worden beschouwd als een nieuwe technologie in vergelijking met .NET Framework, beperken twee belangrijke factoren het vermogen van deze implementatie van .NET om af te wijken van .NET Framework:
Een groot aantal ontwikkelaars is oorspronkelijk ontwikkeld of blijft .NET Framework-toepassingen ontwikkelen. Ze verwachten consistent gedrag in .NET-implementaties.
Met .NET Standard-bibliotheekprojecten kunnen ontwikkelaars bibliotheken maken die gericht zijn op algemene API's die worden gedeeld door .NET Framework en .NET 5 (en .NET Core) en latere versies. Ontwikkelaars verwachten dat een bibliotheek die in een .NET 5-toepassing wordt gebruikt, zich identiek moet gedragen aan dezelfde bibliotheek die wordt gebruikt in een .NET Framework-toepassing.
Naast compatibiliteit in .NET-implementaties verwachten ontwikkelaars een hoog compatibiliteitsniveau in verschillende versies van een bepaalde implementatie van .NET. Met name code die is geschreven voor een eerdere versie van .NET Core, moet naadloos worden uitgevoerd op .NET 5 of een latere versie. Veel ontwikkelaars verwachten zelfs dat de nieuwe API's in nieuw uitgebrachte versies van .NET ook compatibel moeten zijn met de pre-releaseversies waarin deze API's zijn geïntroduceerd.
In dit artikel worden wijzigingen beschreven die van invloed zijn op de compatibiliteit en de manier waarop het .NET-team elk type wijziging evalueert. Begrijpen hoe het .NET-team mogelijke belangrijke wijzigingen nadert, is met name handig voor ontwikkelaars die pull-aanvragen openen die het gedrag van bestaande .NET-API's wijzigen.
In de volgende secties worden de categorieën beschreven van wijzigingen die zijn aangebracht in .NET-API's en de invloed ervan op de compatibiliteit van toepassingen. Wijzigingen zijn toegestaan (✔️), niet toegestaan (❌), of vereisen beoordeling en een evaluatie van hoe voorspelbaar, duidelijk en consistent het vorige gedrag was (❓).
Notitie
- Naast de wijze waarop wijzigingen in .NET-bibliotheken worden geëvalueerd, kunnen bibliotheekontwikkelaars deze criteria ook gebruiken om wijzigingen in hun bibliotheken te evalueren die gericht zijn op meerdere .NET-implementaties en -versies.
- Zie Hoe codewijzigingen van invloed kunnen zijn op de compatibiliteit van de compatibiliteit voor informatie over de compatibiliteitscategorieën, bijvoorbeeld voor voorwaartse en achterwaartse compatibiliteit.
Wijzigingen in het overheidscontract
Wijzigingen in deze categorie wijzigen het openbare oppervlak van een type. De meeste wijzigingen in deze categorie zijn niet toegestaan omdat ze compatibiliteit met eerdere versies schenden (de mogelijkheid van een toepassing die is ontwikkeld met een eerdere versie van een API om uit te voeren zonder opnieuw te compileren op een latere versie).
Typen
✔️ TOEGESTAAN: Een interface-implementatie van een type verwijderen wanneer de interface al door een basistype wordt geïmplementeerd
❓ VEREIST OORDEEL: een nieuwe interface-implementatie toevoegen aan een type
Dit is een acceptabele wijziging omdat deze geen nadelige invloed heeft op bestaande clients. Wijzigingen in het type moeten binnen de grenzen van acceptabele wijzigingen werken die hier zijn gedefinieerd, zodat de nieuwe implementatie acceptabel blijft. Extreme voorzichtigheid is nodig bij het toevoegen van interfaces die rechtstreeks van invloed zijn op de mogelijkheid van een ontwerper of serializer om code of gegevens te genereren die niet op down-level kunnen worden gebruikt. Een voorbeeld is de ISerializable interface.
❓ VEREIST OORDEEL: Introductie van een nieuwe basisklasse
Een type kan worden geïntroduceerd in een hiërarchie tussen twee bestaande typen als het geen nieuwe abstracte leden introduceert of de semantiek of het gedrag van bestaande typen wijzigt. In .NET Framework 2.0 werd de DbConnection klasse bijvoorbeeld een nieuwe basisklasse waarvoor SqlConnectioneerder rechtstreeks Componentwas afgeleid.
✔️ TOEGESTAAN: Een type van de ene assembly naar de andere verplaatsen
De oude assembly moet worden gemarkeerd met de TypeForwardedToAttribute aanduiding die naar de nieuwe assembly verwijst.
✔️ TOEGESTAAN: een structtype wijzigen in een
readonly struct
typeHet wijzigen van een
readonly struct
type in eenstruct
type is niet toegestaan.✔️ TOEGESTAAN: Het verzegelde of abstracte trefwoord toevoegen aan een type wanneer er geen toegankelijke (openbare of beveiligde) constructors zijn
✔️ TOEGESTAAN: De zichtbaarheid van een type uitbreiden
❌NIET TOEGESTAAN: de naamruimte of naam van een type wijzigen
❌NIET TOEGESTAAN: de naam van een openbaar type wijzigen of verwijderen
Hiermee wordt alle code die het hernoemde of verwijderde type gebruikt, verbroken.
Notitie
In zeldzame gevallen kan .NET een openbare API verwijderen. Zie API-verwijdering in .NET voor meer informatie. Voor informatie over . Het ondersteuningsbeleid van NET, zie .NET-ondersteuningsbeleid.
❌NIET TOEGESTAAN: het onderliggende type opsomming wijzigen
Dit is een compilatietijd en gedragswijziging, evenals een binaire wijziging die fouten kan veroorzaken waardoor kenmerkargumenten onherstelbaar kunnen worden.
❌NIET TOEGESTAAN: Een type verzegelen dat eerder niet was verzegeld
❌NIET TOEGESTAAN: Een interface toevoegen aan de set basistypen van een interface
Als een interface een interface implementeert die eerder niet is geïmplementeerd, worden alle typen die de oorspronkelijke versie van de interface hebben geïmplementeerd, verbroken.
❓ VEREIST OORDEEL: Een klasse verwijderen uit de set basisklassen of een interface uit de set geïmplementeerde interfaces
Er is één uitzondering op de regel voor het verwijderen van de interface: u kunt de implementatie van een interface die is afgeleid van de verwijderde interface toevoegen. U kunt bijvoorbeeld verwijderen IDisposable als het type of de interface nu wordt geïmplementeerd IComponent, dat implementeert IDisposable.
❌NIET TOEGESTAAN: een
readonly struct
type wijzigen in een structtypeDe wijziging van een
struct
type in eenreadonly struct
type is echter toegestaan.❌NIET TOEGESTAAN: een structtype wijzigen in een
ref struct
type en omgekeerd❌NIET TOEGESTAAN: De zichtbaarheid van een type verminderen
Het vergroten van de zichtbaarheid van een type is echter toegestaan.
Leden
✔️ TOEGESTAAN: De zichtbaarheid van een lid uitbreiden dat niet virtueel is
✔️ TOEGESTAAN: Een abstract lid toevoegen aan een openbaar type dat geen toegankelijke (openbare of beveiligde) constructors heeft of het type is verzegeld
Het toevoegen van een abstract lid aan een type dat toegankelijke (openbare of beveiligde) constructors heeft en niet is
sealed
toegestaan.✔️ TOEGESTAAN: De zichtbaarheid van een beveiligd lid beperken wanneer het type geen toegankelijke (openbare of beveiligde) constructors heeft of het type is verzegeld
✔️ TOEGESTAAN: Een lid verplaatsen naar een klasse hoger in de hiërarchie dan het type waaruit het is verwijderd
✔️ TOEGESTAAN: Een onderdrukking toevoegen of verwijderen
Als u een onderdrukking introduceert, kunnen eerdere consumenten de onderdrukking overslaan bij het aanroepen van een basis.
✔️ TOEGESTAAN: Een constructor toevoegen aan een klasse, samen met een parameterloze constructor als de klasse eerder geen constructors had
Het toevoegen van een constructor aan een klasse die eerder geen constructors had zonder de parameterloze constructor toe te voegen, is echter niet toegestaan.
✔️ TOEGESTAAN: Van een
ref readonly
naar eenref
retourwaarde wijzigen (met uitzondering van virtuele methoden of interfaces)✔️ TOEGESTAAN: Alleen lezen uit een veld verwijderen, tenzij het statische type van het veld een onveranderbaar waardetype is
✔️ TOEGESTAAN: Een nieuwe gebeurtenis aanroepen die niet eerder is gedefinieerd
❓ VEREIST BEOORDELING: een nieuw exemplaarveld toevoegen aan een type
Deze wijziging heeft invloed op serialisatie.
❌NIET TOEGESTAAN: een openbaar lid of een openbare parameter wijzigen of verwijderen
Hiermee wordt alle code die gebruikmaakt van de naam van het lid of de parameter verwijderd, verbroken.
Dit omvat het verwijderen of wijzigen van de naam van een getter of setter uit een eigenschap, evenals het wijzigen of verwijderen van opsommingsleden.
❌NIET TOEGESTAAN: Een lid toevoegen aan een interface
Als u een implementatie opgeeft, leidt het toevoegen van een nieuw lid aan een bestaande interface niet noodzakelijkerwijs tot compilatiefouten in downstreamassembly's. Niet alle talen ondersteunen echter standaardinterfaceleden (DIM's). In sommige scenario's kan de runtime ook niet bepalen welk standaardinterfacelid moet worden aangeroepen. Om deze redenen wordt het toevoegen van een lid aan een bestaande interface beschouwd als een belangrijke wijziging.
❌NIET TOEGESTAAN: de waarde van een openbaar constante of opsommingslid wijzigen
❌NIET TOEGESTAAN: het type eigenschap, veld, parameter of retourwaarde wijzigen
❌NIET TOEGESTAAN: de volgorde van parameters toevoegen, verwijderen of wijzigen
❌NIET TOEGESTAAN: het trefwoord in, uit of uit een parameter toevoegen of verwijderen
❌NIET TOEGESTAAN: de naam van een parameter wijzigen (inclusief het wijzigen van de hoofdletters)
Dit wordt om twee redenen beschouwd als fouten:
Het onderbreekt late gebonden scenario's, zoals de functie voor late binding in Visual Basic en dynamisch in C#.
De broncompatibiliteit wordt verbroken wanneer ontwikkelaars benoemde argumenten gebruiken.
❌NIET TOEGESTAAN: Wijzigen van een
ref
retourwaarde in eenref readonly
retourwaarde❌± NIET TOEGESTAAN: wijzigen van een
ref readonly
in eenref
retourwaarde op een virtuele methode of interface❌NIET TOEGESTAAN: Abstract toevoegen aan of verwijderen uit een lid
❌NIET TOEGESTAAN: het virtuele trefwoord van een lid verwijderen
❌NIET TOEGESTAAN: het virtuele trefwoord toevoegen aan een lid
Hoewel dit vaak geen belangrijke wijziging is, omdat de C#-compiler meestal callvirt Intermediate Language -instructies (IL) verzendt om niet-virtuele methoden aan te roepen (
callvirt
voert een null-controle uit, terwijl een normale aanroep niet werkt), is dit gedrag om verschillende redenen niet onveranderbaar:C# is niet de enige taal die .NET target.
De C#-compiler probeert steeds vaker te optimaliseren
callvirt
naar een normale aanroep wanneer de doelmethode niet-virtueel is en waarschijnlijk niet null is (zoals een methode die wordt geopend via de operator voor null-doorgifte).
Als u een methode virtueel maakt, betekent dit dat de consumentencode vaak niet-virtueel zou worden aangeroepen.
❌NIET TOEGESTAAN: Een virtueel lid abstract maken
Een virtueel lid biedt een methode-implementatie die kan worden overschreven door een afgeleide klasse. Een abstract lid biedt geen implementatie en moet worden overschreven.
❌NIET TOEGESTAAN: het verzegelde trefwoord toevoegen aan een interfacelid
Als u toevoegt
sealed
aan een standaardinterfacelid, wordt dit niet-virtueel, waardoor de implementatie van een afgeleid type van dat lid niet kan worden aangeroepen.❌NIET TOEGESTAAN: Een abstract lid toevoegen aan een openbaar type dat toegankelijke (openbare of beveiligde) constructors heeft en die niet is verzegeld
❌NIET TOEGESTAAN: het statische trefwoord van een lid toevoegen of verwijderen
❌NIET TOEGESTAAN: Een overbelasting toevoegen die een bestaande overbelasting uitsluit en een ander gedrag definieert
Dit breekt bestaande clients die gebonden waren aan de vorige overbelasting. Als een klasse bijvoorbeeld één versie van een methode heeft die een UInt32methode accepteert, wordt een bestaande consument verbonden met die overbelasting wanneer een Int32 waarde wordt doorgegeven. Als u echter een overbelasting toevoegt die een Int32, bij het opnieuw compileren of gebruiken van late binding accepteert, wordt de compiler nu gebonden aan de nieuwe overbelasting. Als er verschillende gedragsresultaten optreden, is dit een belangrijke wijziging.
❌NIET TOEGESTAAN: Een constructor toevoegen aan een klasse die eerder geen constructor had zonder de parameterloze constructor toe te voegen
❌± NIET TOEGESTAAN: alleen-lezen toevoegen aan een veld
❌NIET TOEGESTAAN: De zichtbaarheid van een lid verminderen
Dit omvat het verminderen van de zichtbaarheid van een beveiligd lid wanneer er toegankelijke (
public
ofprotected
) constructors zijn en het type niet is verzegeld. Als dit niet het geval is, is het beperken van de zichtbaarheid van een beveiligd lid toegestaan.Het vergroten van de zichtbaarheid van een lid is toegestaan.
❌NIET TOEGESTAAN: het type lid wijzigen
De retourwaarde van een methode of het type eigenschap of veld kan niet worden gewijzigd. De handtekening van een methode die een methode retourneert Object , kan bijvoorbeeld niet worden gewijzigd om een String, of omgekeerd, te retourneren.
❌NIET TOEGESTAAN: Een exemplaarveld toevoegen aan een struct die geen niet-openbare velden bevat
Als een struct alleen openbare velden heeft of helemaal geen velden heeft, kunnen bellers de lokale bevolking van dat structtype declareren zonder de constructor van de struct aan te roepen of eerst de lokale waarde te initialiseren,
default(T)
zolang alle openbare velden op de struct zijn ingesteld voordat ze voor het eerst worden gebruikt. Het toevoegen van nieuwe velden (openbaar of niet-openbaar) aan een dergelijke struct is een bronwijziging die fouten optreedt voor deze aanroepers, omdat de compiler nu vereist dat de extra velden worden geïnitialiseerd.Daarnaast is het toevoegen van nieuwe velden ( openbaar of niet-openbaar ) aan een struct zonder velden of alleen openbare velden een binaire wijziging voor bellers die op hun code hebben toegepast
[SkipLocalsInit]
. Omdat de compiler zich niet bewust was van deze velden tijdens het compileren, kan het IL verzenden dat de struct niet volledig initialiseert, waardoor de struct wordt gemaakt op basis van niet-geïnitialiseerde stackgegevens.Als een struct niet-openbare velden bevat, dwingt de compiler al initialisatie af via de constructor of
default(T)
, en het toevoegen van nieuwe exemplaarvelden is geen belangrijke wijziging.❌NIET TOEGESTAAN: Een bestaande gebeurtenis ontslaan toen deze nog nooit werd geactiveerd
Gedragswijzigingen
Assembly's
✔️ TOEGESTAAN: Een assembly draagbaar maken wanneer dezelfde platforms nog steeds worden ondersteund
❌NIET TOEGESTAAN: de naam van een assembly wijzigen
❌NIET TOEGESTAAN: de openbare sleutel van een assembly wijzigen
Eigenschappen, velden, parameters en retourwaarden
✔️ TOEGESTAAN: De waarde van een eigenschap, veld, retourwaarde of outparameter wijzigen in een meer afgeleid type
Een methode die bijvoorbeeld een type Object retourneert, kan een String exemplaar retourneren. (De handtekening van de methode kan echter niet worden gewijzigd.)
✔️ TOEGESTAAN: Het bereik van geaccepteerde waarden voor een eigenschap of parameter verhogen als het lid niet virtueel is
Hoewel het bereik van waarden dat kan worden doorgegeven aan de methode of worden geretourneerd door het lid, kan het parameter- of lidtype niet worden uitgebreid. Terwijl de waarden die aan een methode worden doorgegeven, bijvoorbeeld kunnen worden uitgebreid van 0-124 tot 0-255, kan het parametertype niet veranderen van Byte in Int32.
❌NIET TOEGESTAAN: Het bereik van geaccepteerde waarden voor een eigenschap of parameter verhogen als het lid virtueel is
Deze wijziging onderbreekt bestaande overschreven leden, die niet correct werken voor het uitgebreide bereik van waarden.
❌NIET TOEGESTAAN: het bereik van geaccepteerde waarden voor een eigenschap of parameter verlagen
❌NIET TOEGESTAAN: Het bereik van geretourneerde waarden voor een eigenschap, veld, retourwaarde of outparameter verhogen
❌NIET TOEGESTAAN: De geretourneerde waarden voor een eigenschap, veld, retourwaarde van methode of outparameter wijzigen
❌NIET TOEGESTAAN: de standaardwaarde van een eigenschap, veld of parameter wijzigen
Het wijzigen of verwijderen van een standaardwaarde voor een parameter is geen binair einde. Het verwijderen van een standaardwaarde voor een parameter is een brononderbreking en het wijzigen van een standaardwaarde van een parameter kan leiden tot een gedragsonderbreking na hercompilatie.
Om deze reden is het verwijderen van standaardwaarden voor parameters acceptabel in het specifieke geval van het 'verplaatsen' van deze standaardwaarden naar een nieuwe methode-overbelasting om dubbelzinnigheid te voorkomen. Denk bijvoorbeeld aan een bestaande methode
MyMethod(int a = 1)
. Als u een overbelasting vanMyMethod
twee optionele parametersa
introduceert enb
u de compatibiliteit kunt behouden door de standaardwaarde vana
de nieuwe overbelasting te verplaatsen. Nu zijnMyMethod(int a)
de twee overbelastingen enMyMethod(int a = 1, int b = 2)
. Met dit patroon kunt uMyMethod()
compileren.❌NIET TOEGESTAAN: de precisie van een numerieke retourwaarde wijzigen
❓ VEREIST OORDEEL: Een wijziging in de parsering van invoer en het genereren van nieuwe uitzonderingen (zelfs als het parseringsgedrag niet is opgegeven in de documentatie
Uitzonderingen
✔️ TOEGESTAAN: Een meer afgeleide uitzondering genereren dan een bestaande uitzondering
Omdat de nieuwe uitzondering een subklasse van een bestaande uitzondering is, blijft de eerdere uitzonderingsverwerkingscode de uitzondering verwerken. In .NET Framework 4 begonnen bijvoorbeeld methoden voor het maken en ophalen van cultuur een CultureNotFoundException in plaats van een ArgumentException als de cultuur niet kon worden gevonden. Omdat CultureNotFoundException dit is afgeleid van ArgumentException, is dit een acceptabele wijziging.
✔️ TOEGESTAAN: Een specifiekere uitzondering genereren dan NotSupportedException, NotImplementedExceptionNullReferenceException
✔️ TOEGESTAAN: Een uitzondering genereren die als onherstelbaar wordt beschouwd
Onherstelbare uitzonderingen mogen niet worden gevangen, maar moeten in plaats daarvan worden afgehandeld door een catch-all-handler op hoog niveau. Daarom wordt verwacht dat gebruikers geen code hebben die deze expliciete uitzonderingen ondervangt. De onherstelbare uitzonderingen zijn:
✔️ TOEGESTAAN: Een nieuwe uitzondering genereren in een nieuw codepad
De uitzondering moet alleen van toepassing zijn op een nieuw codepad dat wordt uitgevoerd met nieuwe parameterwaarden of -status en die niet kan worden uitgevoerd door bestaande code die is gericht op de vorige versie.
✔️ TOEGESTAAN: Een uitzondering verwijderen om robuuster gedrag of nieuwe scenario's mogelijk te maken
Een methode die eerder alleen positieve waarden afhandelde en een ArgumentOutOfRangeException andere methode gooide, kan bijvoorbeeld
Divide
worden gewijzigd om zowel negatieve als positieve waarden te ondersteunen zonder een uitzondering te genereren.✔️ TOEGESTAAN: De tekst van een foutbericht wijzigen
Ontwikkelaars mogen niet afhankelijk zijn van de tekst van foutberichten, die ook veranderen op basis van de cultuur van de gebruiker.
❌NIET TOEGESTAAN: Een uitzondering genereren in een ander geval dat hierboven niet wordt vermeld
❌NIET TOEGESTAAN: Een uitzondering verwijderen in een ander geval dat hierboven niet wordt vermeld
Kenmerken
✔️ TOEGESTAAN: De waarde wijzigen van een kenmerk dat niet waarneembaar is
❌NIET TOEGESTAAN: de waarde wijzigen van een kenmerk dat waarneembaar is
❓ VEREIST BEOORDELING: Een kenmerk verwijderen
In de meeste gevallen is het verwijderen van een kenmerk (zoals NonSerializedAttribute) een belangrijke wijziging.
Platformondersteuning
✔️ TOEGESTAAN: Ondersteuning van een bewerking op een platform dat eerder niet werd ondersteund
❌NIET TOEGESTAAN: er is geen ondersteuning of vereist nu een specifiek servicepack voor een bewerking die eerder op een platform werd ondersteund
Interne implementatiewijzigingen
❓ VEREIST BEOORDELING: het oppervlak van een intern type wijzigen
Dergelijke wijzigingen zijn over het algemeen toegestaan, hoewel ze privé-reflectie breken. In sommige gevallen, waarbij populaire bibliotheken van derden of een groot aantal ontwikkelaars afhankelijk zijn van de interne API's, zijn dergelijke wijzigingen mogelijk niet toegestaan.
❓ VEREIST OORDEEL: de interne implementatie van een lid wijzigen
Deze wijzigingen zijn over het algemeen toegestaan, hoewel ze privé-reflectie verbreken. In sommige gevallen is klantcode vaak afhankelijk van persoonlijke reflectie of wanneer de wijziging onbedoelde bijwerkingen introduceert, zijn deze wijzigingen mogelijk niet toegestaan.
✔️ TOEGESTAAN: De prestaties van een bewerking verbeteren
De mogelijkheid om de prestaties van een bewerking te wijzigen is essentieel, maar dergelijke wijzigingen kunnen code breken die afhankelijk is van de huidige snelheid van een bewerking. Dit geldt met name voor code die afhankelijk is van de timing van asynchrone bewerkingen. De prestatiewijziging mag geen effect hebben op ander gedrag van de BETREFFENDE API; anders wordt de wijziging onderbroken.
✔️ TOEGESTAAN: Indirect (en vaak nadelig) het wijzigen van de prestaties van een bewerking
Als de betreffende wijziging om een andere reden niet wordt gecategoriseerd als breuk, is dit acceptabel. Vaak moeten er acties worden uitgevoerd die extra bewerkingen kunnen bevatten of die nieuwe functionaliteit toevoegen. Dit is vrijwel altijd van invloed op de prestaties, maar kan essentieel zijn om de betreffende API naar verwachting te laten functioneren.
❌NIET TOEGESTAAN: een synchrone API wijzigen in asynchroon (en omgekeerd)
Codewijzigingen
✔️ TOEGESTAAN: Parameters toevoegen aan een parameter
❌NIET TOEGESTAAN: een struct wijzigen in een klasse en omgekeerd
❌NIET TOEGESTAAN: het ingeschakelde trefwoord toevoegen aan een codeblok
Deze wijziging kan ertoe leiden dat code die eerder is uitgevoerd, een OverflowException en onaanvaardbaar is.
❌NIET TOEGESTAAN: Parameters verwijderen uit een parameter
❌NIET TOEGESTAAN: de volgorde wijzigen waarin gebeurtenissen worden geactiveerd
Ontwikkelaars kunnen redelijkerwijs verwachten dat gebeurtenissen in dezelfde volgorde worden geactiveerd en dat ontwikkelaarscode vaak afhankelijk is van de volgorde waarin gebeurtenissen worden geactiveerd.
❌NIET TOEGESTAAN: de verhoging van een gebeurtenis voor een bepaalde actie verwijderen
❌NIET TOEGESTAAN: Het aantal keren wijzigen dat bepaalde gebeurtenissen worden aangeroepen
❌NIET TOEGESTAAN: Het toevoegen FlagsAttribute aan een opsommingstype