Notitie
Voor toegang tot deze pagina is autorisatie vereist. U kunt proberen u aan te melden of de directory te wijzigen.
Voor toegang tot deze pagina is autorisatie vereist. U kunt proberen de mappen te wijzigen.
Slechte app-prestaties worden op veel manieren gepresenteerd. Het kan ervoor zorgen dat een app niet reageert, kan leiden tot traag schuiven en kan de levensduur van de batterij van het apparaat verminderen. Het optimaliseren van de prestaties omvat echter meer dan alleen het implementeren van efficiënte code. De gebruikerservaring van app-prestaties moet ook worden overwogen. Als u er bijvoorbeeld voor zorgt dat bewerkingen worden uitgevoerd zonder dat de gebruiker andere activiteiten uitvoert, kan dit helpen om de gebruikerservaring te verbeteren.
Er zijn veel technieken voor het verhogen van de prestaties en waargenomen prestaties van .NET Multi-Platform App UI-apps (.NET MAUI). Gezamenlijk kunnen deze technieken de hoeveelheid werk die door een CPU wordt uitgevoerd aanzienlijk verminderen en de hoeveelheid geheugen die door een app wordt verbruikt.
Een profiler gebruiken
Bij het ontwikkelen van een app is het belangrijk om alleen code te optimaliseren zodra deze is geprofileerd. Profilering is een techniek om te bepalen waar code-optimalisaties het grootste effect hebben bij het verminderen van prestatieproblemen. De profiler houdt het geheugengebruik van de app bij en registreert de actieve tijd van methoden in de app. Deze gegevens helpen bij het navigeren door de uitvoeringspaden van de app en de uitvoeringskosten van de code, zodat de beste mogelijkheden voor optimalisatie kunnen worden gedetecteerd.
.NET MAUI-apps kunnen worden geprofileerd met behulp van dotnet-trace
op Android, iOS en Mac en Windows, en met PerfView in Windows. Zie voor meer informatie Profilering .NET MAUI-apps.
De volgende aanbevolen procedures worden aanbevolen bij het profileren van een app:
- Vermijd het profileren van een app in een simulator, omdat de simulator de prestaties van de app kan verstoren.
- In het ideale geval moet profilering worden uitgevoerd op verschillende apparaten, omdat prestatiemetingen op het ene apparaat niet altijd de prestatiekenmerken van andere apparaten tonen. Profilering moet echter minimaal worden uitgevoerd op een apparaat met de laagste verwachte specificatie.
- Sluit alle andere apps om ervoor te zorgen dat de volledige impact van de app die wordt geprofileerd, wordt gemeten in plaats van de andere apps.
Gecompileerde bindingen gebruiken
Gecompileerde bindingen verbeteren de prestaties van gegevensbindingen in .NET MAUI-apps door bindingexpressies tijdens het compileren op te lossen, in plaats van tijdens runtime met weerspiegeling. Door een bindingsexpressie te compileren, wordt gecompileerde code gegenereerd die doorgaans een binding 8-20 keer sneller oplost dan het gebruik van een klassieke binding. Zie Gecompileerde bindingenvoor meer informatie.
Onnodige bindingen verminderen
Gebruik geen bindingen voor inhoud die eenvoudig statisch kan worden ingesteld. Er is geen voordeel in bindingsgegevens die niet hoeven te worden gebonden, omdat bindingen niet kostenefficiënt zijn. Het instellen van Button.Text = "Accept"
heeft bijvoorbeeld minder overhead dan het binden van Button.Text aan een viewmodel string
eigenschap met de waarde 'Accepteren'.
De juiste indeling kiezen
Een indeling die meerdere kinderen kan weergeven, maar die slechts één kind heeft, is verspilling. In het volgende voorbeeld ziet u een VerticalStackLayout met één kind:
<ContentPage xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
x:Class="MyMauiApp.MainPage">
<VerticalStackLayout>
<Image Source="waterfront.jpg" />
</VerticalStackLayout>
</ContentPage>
Dit is verspilling en het VerticalStackLayout element moet worden verwijderd, zoals wordt weergegeven in het volgende voorbeeld:
<ContentPage xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
x:Class="MyMauiApp.MainPage">
<Image Source="waterfront.jpg" />
</ContentPage>
Probeer bovendien niet om het uiterlijk van een specifieke indeling te reproduceren met behulp van combinaties van andere indelingen, omdat dit resulteert in onnodige indelingsberekeningen die worden uitgevoerd. Probeer bijvoorbeeld geen Grid indeling te reproduceren met behulp van een combinatie van HorizontalStackLayout elementen. In het volgende voorbeeld ziet u een voorbeeld van deze slechte praktijk:
<ContentPage xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
x:Class="MyMauiApp.MainPage">
<VerticalStackLayout>
<HorizontalStackLayout>
<Label Text="Name:" />
<Entry Placeholder="Enter your name" />
</HorizontalStackLayout>
<HorizontalStackLayout>
<Label Text="Age:" />
<Entry Placeholder="Enter your age" />
</HorizontalStackLayout>
<HorizontalStackLayout>
<Label Text="Occupation:" />
<Entry Placeholder="Enter your occupation" />
</HorizontalStackLayout>
<HorizontalStackLayout>
<Label Text="Address:" />
<Entry Placeholder="Enter your address" />
</HorizontalStackLayout>
</VerticalStackLayout>
</ContentPage>
Dit is verspilling omdat onnodige indelingsberekeningen worden uitgevoerd. In plaats daarvan kan de gewenste indeling beter worden bereikt met behulp van een Grid, zoals wordt weergegeven in het volgende voorbeeld:
<ContentPage xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
x:Class="MyMauiApp.MainPage">
<Grid ColumnDefinitions="100,*"
RowDefinitions="30,30,30,30">
<Label Text="Name:" />
<Entry Grid.Column="1"
Placeholder="Enter your name" />
<Label Grid.Row="1"
Text="Age:" />
<Entry Grid.Row="1"
Grid.Column="1"
Placeholder="Enter your age" />
<Label Grid.Row="2"
Text="Occupation:" />
<Entry Grid.Row="2"
Grid.Column="1"
Placeholder="Enter your occupation" />
<Label Grid.Row="3"
Text="Address:" />
<Entry Grid.Row="3"
Grid.Column="1"
Placeholder="Enter your address" />
</Grid>
</ContentPage>
Afbeeldingsbronnen optimaliseren
Afbeeldingen zijn enkele van de duurste resources die apps gebruiken en worden vaak vastgelegd met hoge resoluties. Hoewel hierdoor levendige afbeeldingen vol details worden gemaakt, vereisen apps die dergelijke afbeeldingen weergeven doorgaans meer CPU-gebruik om de afbeelding te decoderen en meer geheugen om de gedecodeerde afbeelding op te slaan. Het is verspilling om een afbeelding met een hoge resolutie in het geheugen te decoderen wanneer deze omlaag wordt geschaald naar een kleiner formaat voor weergave. Verminder in plaats daarvan het CPU-gebruik en de geheugenvoetafdruk door versies van opgeslagen afbeeldingen te maken die dicht bij de voorspelde weergavegrootten liggen. Een afbeelding die in een lijstweergave wordt weergegeven, moet bijvoorbeeld waarschijnlijk een lagere resolutie hebben dan een afbeelding die op volledig scherm wordt weergegeven.
Bovendien moeten afbeeldingen alleen worden gemaakt wanneer dat nodig is en moeten ze worden verwijderd zodra de app ze niet meer nodig heeft. Als een app bijvoorbeeld een afbeelding weergeeft door de gegevens uit een stream te lezen, moet u ervoor zorgen dat de stream alleen wordt gemaakt wanneer deze vereist is en ervoor zorgt dat de stream wordt vrijgegeven wanneer deze niet meer nodig is. Dit kan worden bereikt door de stream te maken wanneer de pagina wordt gemaakt, of wanneer de Page.Appearing gebeurtenis wordt geactiveerd en vervolgens de stream af te voeren wanneer de Page.Disappearing gebeurtenis wordt geactiveerd.
Wanneer u een afbeelding downloadt voor weergave met de methode ImageSource.FromUri(Uri), zorg er dan voor dat de gedownloade afbeelding voor een geschikte periode in de cache blijft. Zie Image Cachingvoor meer informatie.
Het aantal elementen op een pagina verminderen
Door het aantal elementen op een pagina te verminderen, wordt de pagina sneller weergegeven. Er zijn twee belangrijke technieken om dit te bereiken. Het eerste is om elementen te verbergen die niet zichtbaar zijn. De eigenschap IsVisible van elk element bepaalt of het element zichtbaar moet zijn op het scherm. Als een element niet zichtbaar is omdat het verborgen is achter andere elementen, verwijdert u het element of stelt u de eigenschap IsVisible
ervan in op false
. Door de eigenschap IsVisible
van een element in te stellen op false
, blijft het element in de visuele structuur, maar wordt het uitgesloten van render- en lay-outberekeningen.
De tweede techniek is het verwijderen van overbodige elementen. Hieronder ziet u bijvoorbeeld een pagina-indeling met meerdere Label elementen:
<VerticalStackLayout>
<VerticalStackLayout Padding="20,20,0,0">
<Label Text="Hello" />
</VerticalStackLayout>
<VerticalStackLayout Padding="20,20,0,0">
<Label Text="Welcome to the App!" />
</VerticalStackLayout>
<VerticalStackLayout Padding="20,20,0,0">
<Label Text="Downloading Data..." />
</VerticalStackLayout>
</VerticalStackLayout>
Dezelfde pagina-indeling kan worden onderhouden met een gereduceerd aantal elementen, zoals wordt weergegeven in het volgende voorbeeld:
<VerticalStackLayout Padding="20,35,20,20"
Spacing="25">
<Label Text="Hello" />
<Label Text="Welcome to the App!" />
<Label Text="Downloading Data..." />
</VerticalStackLayout>
De woordenlijstgrootte van de toepassingsresource verkleinen
Alle resources die in de app worden gebruikt, moeten worden opgeslagen in de resourcewoordenlijst van de app om duplicatie te voorkomen. Dit helpt bij het verminderen van de hoeveelheid XAML die in de app moet worden geparseerd. In het volgende voorbeeld ziet u de HeadingLabelStyle
resource, die voor de hele app wordt gebruikt en dus is gedefinieerd in de resourcewoordenlijst van de app:
<Application xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
x:Class="MyMauiApp.App">
<Application.Resources>
<Style x:Key="HeadingLabelStyle"
TargetType="Label">
<Setter Property="HorizontalOptions"
Value="Center" />
<Setter Property="FontSize"
Value="Large" />
<Setter Property="TextColor"
Value="Red" />
</Style>
</Application.Resources>
</Application>
XAML die specifiek is voor een pagina, mag echter niet worden opgenomen in de resourcewoordenlijst van de app, omdat de resources vervolgens worden geparseerd bij het opstarten van de app in plaats van wanneer dat nodig is voor een pagina. Als een resource wordt gebruikt door een pagina die niet de opstartpagina is, moet deze in de resourcewoordenlijst voor die pagina worden geplaatst, zodat u de XAML kunt verminderen die wordt geparseerd wanneer de app wordt gestart. In het volgende voorbeeld ziet u de HeadingLabelStyle
resource, die zich slechts op één pagina bevindt en dus is gedefinieerd in de resourcewoordenlijst van de pagina:
<ContentPage xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
x:Class="MyMauiApp.MainPage">
<ContentPage.Resources>
<Style x:Key="HeadingLabelStyle"
TargetType="Label">
<Setter Property="HorizontalOptions"
Value="Center" />
<Setter Property="FontSize"
Value="Large" />
<Setter Property="TextColor"
Value="Red" />
</Style>
</ContentPage.Resources>
...
</ContentPage>
Zie Style-apps met XAML-voor meer informatie over app-resources.
De grootte van de app verkleinen
Wanneer .NET MAUI uw app bouwt, kan een linker met de naam ILLink- worden gebruikt om de totale grootte van de app te verminderen. ILLink vermindert de grootte door de tussenliggende code te analyseren die door de compiler wordt geproduceerd. Hiermee worden ongebruikte methoden, eigenschappen, velden, gebeurtenissen, structs en klassen verwijderd om een app te produceren die alleen code- en assemblyafhankelijkheden bevat die nodig zijn om de app uit te voeren.
Zie Een Android-app koppelen, Een iOS-app koppelenen Een Mac Catalyst-app koppelenvoor meer informatie over het configureren van het linkergedrag.
De activeringsperiode van de app verminderen
Alle apps hebben een activeringsperiode. Dit is de tijd tussen het moment waarop de app wordt gestart en wanneer de app klaar is voor gebruik. Deze activeringsperiode biedt gebruikers de eerste indruk van de app, en daarom is het belangrijk om de activeringsperiode en de perceptie van de gebruiker ervan te verminderen, zodat ze een gunstige eerste indruk van de app kunnen krijgen.
Voordat een app de oorspronkelijke gebruikersinterface weergeeft, moet deze een welkomstscherm opgeven om de gebruiker aan te geven dat de app wordt gestart. Als de app de oorspronkelijke gebruikersinterface niet snel kan weergeven, moet het welkomstscherm worden gebruikt om de gebruiker te informeren over de voortgang van de activeringsperiode, om er zeker van te zijn dat de app niet is vastgelopen. Deze zekerheid kan een voortgangsbalk of een vergelijkbare controle zijn.
Tijdens de activeringsperiode voeren apps activeringslogica uit, waaronder vaak het laden en verwerken van resources. De activeringsperiode kan worden verminderd door ervoor te zorgen dat de vereiste resources in de app worden verpakt, in plaats van op afstand te worden opgehaald. In sommige gevallen kan het bijvoorbeeld handig zijn tijdens de activeringsperiode om lokaal opgeslagen tijdelijke aanduidingsgegevens te laden. Zodra de eerste gebruikersinterface wordt weergegeven en de gebruiker kan communiceren met de app, kunnen de tijdelijke aanduidingsgegevens geleidelijk worden vervangen door een externe bron. Bovendien moet de activeringslogica van de app alleen werk uitvoeren dat nodig is om de gebruiker de app te laten gebruiken. Deze methode kan helpen als het laden van extra assembly's vertraagd is, omdat assembly's de eerste keer worden geladen wanneer ze gebruikt worden.
Een container voor afhankelijkheidsinjectie zorgvuldig kiezen
Containers voor afhankelijkheidsinjectie introduceren extra prestatiebeperkingen in mobiele apps. Het registreren en oplossen van typen met een container heeft een prestatiekosten vanwege het gebruik van weerspiegeling van de container voor het maken van elk type, met name als afhankelijkheden worden gereconstrueerd voor elke paginanavigatie in de app. Als er veel of diepe afhankelijkheden zijn, kunnen de kosten van het maken aanzienlijk toenemen. Daarnaast kan typeregistratie, die meestal plaatsvindt tijdens het opstarten van de app, een merkbare invloed hebben op de opstarttijd, afhankelijk van de gebruikte container. Zie Afhankelijkheidsinjectievoor meer informatie over afhankelijkheidsinjectie in .NET MAUI-apps.
Als alternatief kan afhankelijkheidsinjectie beter presteren door deze handmatig te implementeren met behulp van factory's.
Shell-apps maken
.NET MAUI Shell-apps bieden een gerichte navigatie-ervaring op basis van flyouts en tabbladen. Als uw app-gebruikerservaring kan worden geïmplementeerd met Shell, is het handig om dit te doen. Shell-apps helpen om een slechte opstartervaring te voorkomen, omdat pagina's op aanvraag worden gemaakt als reactie op navigatie in plaats van bij het opstarten van apps, wat gebeurt met apps die gebruikmaken van een TabbedPage. Zie Shell-overzichtvoor meer informatie.
Prestaties van ListView optimaliseren
Wanneer u ListViewgebruikt, zijn er een aantal gebruikerservaringen die moeten worden geoptimaliseerd:
- Initialisatie: het tijdsinterval dat begint wanneer het bedieningselement wordt gemaakt en eindigt wanneer items op het scherm worden weergegeven.
- scrollen: de mogelijkheid om door de lijst te bladeren en ervoor te zorgen dat de gebruikersinterface niet achterloopt op aanraakbewegingen.
- Interactie voor het toevoegen, verwijderen en selecteren van items.
Het ListView besturingselement vereist dat een app gegevens- en celsjablonen levert. Hoe dit wordt bereikt, heeft een grote invloed op de prestaties van het besturingselement. Zie Cachegegevensvoor meer informatie.
Asynchrone programmering gebruiken
De algehele reactiesnelheid van uw app kan worden verbeterd en prestatieknelpunten worden vaak vermeden door asynchrone programmering te gebruiken. In .NET is het op taken gebaseerde Asynchrone patroon (TAP) het aanbevolen ontwerppatroon voor asynchrone bewerkingen. Onjuist gebruik van de TAP kan echter leiden tot niet-presterende apps.
Basisprincipes
De volgende algemene richtlijnen moeten worden gevolgd wanneer u de TAP gebruikt:
- Inzicht in de levenscyclus van taken, die wordt vertegenwoordigd door de opsomming
TaskStatus
. Zie De betekenis van TaskStatus en Taakstatusvoor meer informatie. - Gebruik de methode
Task.WhenAll
om asynchroon te wachten tot meerdere asynchrone bewerkingen zijn voltooid, in plaats van een reeks asynchrone bewerkingen afzonderlijk teawait
. Zie Task.WhenAllvoor meer informatie. - Gebruik de methode
Task.WhenAny
om asynchroon te wachten tot een van de asynchrone bewerkingen is voltooid. Zie Task.WhenAnyvoor meer informatie. - Gebruik de methode
Task.Delay
om eenTask
-object te produceren dat na de opgegeven tijd is voltooid. Dit is handig voor scenario's zoals polling voor gegevens en het vertragen van het verwerken van gebruikersinvoer voor een vooraf bepaalde tijd. Zie Task.Delayvoor meer informatie. - Voer intensieve synchrone CPU-bewerkingen uit op de threadgroep met de methode
Task.Run
. Deze methode is een snelkoppeling voor deTaskFactory.StartNew
methode, met de meest optimale argumentenet. Zie Task.Runvoor meer informatie. - Vermijd het maken van asynchrone constructors. Gebruik in plaats daarvan levenscyclus-gebeurtenissen of afzonderlijke initialisatielogica om elke initialisatie correct te
await
. Zie Async Constructors op blog.stephencleary.com voor meer informatie. - Gebruik het patroon van de 'luie taak' om te voorkomen dat je tijdens het opstarten van de app moet wachten op het afronden van asynchrone bewerkingen. Zie AsyncLazyvoor meer informatie.
- Maak een taakwikkelaar voor bestaande asynchrone bewerkingen, die de TAP niet gebruiken, door
TaskCompletionSource<T>
objecten te maken. Deze objecten krijgen de voordelen vanTask
programmeerbaarheid en stellen u in staat om de levensduur en voltooiing van de bijbehorendeTask
te beheren. Zie The Nature of TaskCompletionSourcevoor meer informatie. - Retourneer een
Task
-object in plaats van een gewachtTask
object te retourneren wanneer het resultaat van een asynchrone bewerking niet hoeft te worden verwerkt. Dit is beter presterend omdat er minder contextwisselingen worden uitgevoerd. - Gebruik de TPL-gegevensstroombibliotheek (Task Parallel Library) in scenario's zoals het verwerken van gegevens wanneer deze beschikbaar komen of wanneer u meerdere bewerkingen hebt die asynchroon met elkaar moeten communiceren. Zie gegevensstroom (taakparallelbibliotheek)voor meer informatie.
GEBRUIKERSINTERFACE
De volgende richtlijnen moeten worden gevolgd bij het gebruik van de TAP met UI-besturingselementen:
Roep een asynchrone versie van een API aan als deze beschikbaar is. Hierdoor blijft de UI-thread gedeblokkeerd, waardoor de gebruikerservaring met de app kan worden verbeterd.
Werk UI-elementen bij met gegevens uit asynchrone bewerkingen op de UI-thread om te voorkomen dat uitzonderingen worden gegenereerd. Updates van de eigenschap
ListView.ItemsSource
worden echter automatisch naar de UI-thread verzonden. Zie Een thread maken op de UI-threadvoor informatie over het bepalen of code wordt uitgevoerd op de UI-thread.Belangrijk
Alle besturingseigenschappen die via gegevensbinding worden bijgewerkt, worden automatisch aan de UI-thread gekoppeld.
Foutafhandeling
De volgende richtlijnen voor foutafhandeling moeten worden gevolgd wanneer u de TAP gebruikt:
- Meer informatie over het verwerken van asynchrone uitzonderingen. Niet-verwerkte uitzonderingen die worden gegenereerd door code die asynchroon wordt uitgevoerd, worden doorgegeven aan de aanroepende thread, behalve in bepaalde scenario's. Zie Uitzonderingsafhandeling (Taakparallelbibliotheek)voor meer informatie.
- Vermijd het maken van
async void
methoden en maak in plaats daarvanasync Task
methoden. Hierdoor is foutafhandeling, composabiliteit en testbaarheid eenvoudiger. De uitzondering op deze richtlijn is asynchrone eventhandlers, dievoid
moeten retourneren. Zie Vermijd Async Voidvoor meer informatie. - Combineer geen blokkerende en asynchrone code door de
Task.Wait
,Task.Result
ofGetAwaiter().GetResult
methoden aan te roepen, omdat ze kunnen leiden tot een impasse. Als deze richtlijn echter moet worden geschonden, is de voorkeursbenadering het aanroepen van deGetAwaiter().GetResult
methode, omdat de taak-uitzonderingen behouden blijven. Zie Async All the Way en Task Exception Handling in .NET 4.5voor meer informatie. - Gebruik waar mogelijk de methode
ConfigureAwait
om contextloze code te maken. Code zonder context heeft betere prestaties voor mobiele apps en is een handige techniek voor het voorkomen van impasses bij het werken met een gedeeltelijk asynchrone codebasis. Zie Context configurerenvoor meer informatie. - Gebruik vervolgtaken voor functionaliteit, zoals het verwerken van uitzonderingen die zijn gegenereerd door de vorige asynchrone bewerking en het annuleren van een vervolgbewerking voordat deze wordt gestart of terwijl deze wordt uitgevoerd. Zie Taken koppelen met behulp van doorlopende takenvoor meer informatie.
- Gebruik een asynchrone ICommand implementatie wanneer asynchrone bewerkingen worden aangeroepen vanuit de ICommand. Dit zorgt ervoor dat eventuele uitzonderingen in de asynchrone opdrachtlogica kunnen worden verwerkt. Zie Async Programming: Patterns for Asynchronous MVVM Applications: Commandsvoor meer informatie.
Stel de kosten van het maken van objecten uit
Luie initialisatie kan worden gebruikt om het maken van een object uit te stellen totdat het voor het eerst wordt gebruikt. Deze techniek wordt voornamelijk gebruikt om de prestaties te verbeteren, berekeningen te voorkomen en geheugenvereisten te verminderen.
Overweeg om luie initialisatie te gebruiken voor objecten die duur zijn om te maken in de volgende scenario's:
- De app gebruikt het object mogelijk niet.
- Andere dure bewerkingen moeten worden voltooid voordat het object wordt gemaakt.
De Lazy<T>
-klasse wordt gebruikt om een lui geïnitialiseerd type te definiëren, zoals wordt weergegeven in het volgende voorbeeld:
void ProcessData(bool dataRequired = false)
{
Lazy<double> data = new Lazy<double>(() =>
{
return ParallelEnumerable.Range(0, 1000)
.Select(d => Compute(d))
.Aggregate((x, y) => x + y);
});
if (dataRequired)
{
if (data.Value > 90)
{
...
}
}
}
double Compute(double x)
{
...
}
Lazy-initialisatie vindt plaats de eerste keer dat de eigenschap Lazy<T>.Value
wordt geopend. Het verpakte type wordt gemaakt en geretourneerd bij eerste toegang en opgeslagen voor eventuele toekomstige toegang.
Zie Lazy Initializationvoor meer informatie over luie initialisatie.
IDisposable-bronnen vrijgeven
De IDisposable
-interface biedt een mechanisme voor het vrijgeven van resources. Het biedt een Dispose
methode die moet worden geïmplementeerd om expliciet resources vrij te geven.
IDisposable
is geen destructor, en moet alleen in de volgende omstandigheden worden geïmplementeerd:
- Wanneer de klasse eigenaar is van niet-beheerde resources. Niet-beheerde resources die typisch vrijgegeven moeten worden, zijn onder andere bestanden, streams en netwerkverbindingen.
- Wanneer de klasse eigenaar is van beheerde
IDisposable
resources.
Typeconsumenten kunnen vervolgens de IDisposable.Dispose
-implementatie aanroepen om middelen vrij te maken wanneer het exemplaar niet meer nodig is. Er zijn twee manieren om dit te bereiken:
- Door het
IDisposable
object in eenusing
instructie te verpakken. - Door de aanroep naar
IDisposable.Dispose
in eentry
/finally
blok te verpakken.
Het IDisposable-object inpakken in een using-instructie
In het volgende voorbeeld ziet u hoe u een IDisposable
-object verpakt in een using
-instructie:
public void ReadText(string filename)
{
string text;
using (StreamReader reader = new StreamReader(filename))
{
text = reader.ReadToEnd();
}
...
}
De StreamReader
-klasse implementeert IDisposable
en de using
-instructie biedt een handige syntaxis waarmee de StreamReader.Dispose
methode voor het StreamReader
object wordt aangeroepen voordat het buiten het bereik valt. Binnen het using
blok is het StreamReader
-object alleen lezen en kan het niet opnieuw worden toegewezen. De using
-instructie zorgt er ook voor dat de Dispose
methode wordt aangeroepen, zelfs als er een uitzondering optreedt, omdat de compiler de tussenliggende taal (IL) implementeert voor een try
/finally
blok.
Omhul de aanroep van IDisposable.Dispose in een try/finally-blok.
In het volgende voorbeeld ziet u hoe u de aanroep terugloopt naar IDisposable.Dispose
in een try
/finally
blok:
public void ReadText(string filename)
{
string text;
StreamReader reader = null;
try
{
reader = new StreamReader(filename);
text = reader.ReadToEnd();
}
finally
{
if (reader != null)
reader.Dispose();
}
...
}
De StreamReader
-klasse implementeert IDisposable
en het finally
blok roept de StreamReader.Dispose
methode aan om de resource vrij te geven. Zie IDisposable Interfacevoor meer informatie.
Afmelden voor gebeurtenissen
Om geheugenlekken te voorkomen, moeten gebeurtenissen worden afgemeld voordat het abonneeobject wordt verwijderd. Totdat de gebeurtenis is afgemeld, heeft de gemachtigde voor de gebeurtenis in het publicatieobject een verwijzing naar de gemachtigde die de gebeurtenis-handler van de abonnee inkapselt. Zolang het publicatieobject deze verwijzing bevat, maakt garbagecollection het geheugen van het abonneeobject niet vrij.
In het volgende voorbeeld ziet u hoe u zich kunt afmelden voor een gebeurtenis:
public class Publisher
{
public event EventHandler MyEvent;
public void OnMyEventFires()
{
if (MyEvent != null)
MyEvent(this, EventArgs.Empty);
}
}
public class Subscriber : IDisposable
{
readonly Publisher _publisher;
public Subscriber(Publisher publish)
{
_publisher = publish;
_publisher.MyEvent += OnMyEventFires;
}
void OnMyEventFires(object sender, EventArgs e)
{
Debug.WriteLine("The publisher notified the subscriber of an event");
}
public void Dispose()
{
_publisher.MyEvent -= OnMyEventFires;
}
}
De Subscriber
-klasse meldt zich af voor het evenement in zijn Dispose
-methode.
Verwijzingscycli kunnen ook optreden bij het gebruik van gebeurtenishandlers en lambda-expressies, omdat lambda-expressies naar objecten kunnen verwijzen en die actief kunnen houden. Daarom kan een verwijzing naar de anonieme methode worden opgeslagen in een veld en worden gebruikt om u af te melden voor de gebeurtenis, zoals wordt weergegeven in het volgende voorbeeld:
public class Subscriber : IDisposable
{
readonly Publisher _publisher;
EventHandler _handler;
public Subscriber(Publisher publish)
{
_publisher = publish;
_handler = (sender, e) =>
{
Debug.WriteLine("The publisher notified the subscriber of an event");
};
_publisher.MyEvent += _handler;
}
public void Dispose()
{
_publisher.MyEvent -= _handler;
}
}
Het veld _handler
onderhoudt de verwijzing naar de anonieme methode en wordt gebruikt voor gebeurtenisabonnementen en afmelden.
Vermijd sterke kringverwijzingen op iOS en Mac Catalyst
In sommige situaties is het mogelijk om sterke referentiecycli te maken die kunnen voorkomen dat objecten hun geheugen door de garbage collector wordt teruggewonnen. Denk bijvoorbeeld aan het geval dat een NSObject-afgeleide subklasse, zoals een klasse die wordt overgenomen van UIView, wordt toegevoegd aan een NSObject
-afgeleide container en sterk wordt verwezen vanuit Objective-C, zoals wordt weergegeven in het volgende voorbeeld:
class Container : UIView
{
public void Poke()
{
// Call this method to poke this object
}
}
class MyView : UIView
{
Container _parent;
public MyView(Container parent)
{
_parent = parent;
}
void PokeParent()
{
_parent.Poke();
}
}
var container = new Container();
container.AddSubview(new MyView(container));
Wanneer deze code het Container
-exemplaar maakt, krijgt het C#-object een sterke verwijzing naar een Objective-C-object. Op dezelfde manier heeft het MyView
exemplaar ook een sterke verwijzing naar een Objective-C-object.
Bovendien verhoogt de aanroep naar container.AddSubview
het aantal verwijzingen op het onbeheerde MyView
exemplaar. Als dit gebeurt, maakt de .NET voor iOS-runtime een GCHandle
exemplaar om het MyView
-object in beheerde code actief te houden, omdat er geen garantie is dat beheerde objecten er een verwijzing naar behouden. Vanuit het perspectief van beheerde code zou het MyView
-object worden vrijgemaakt na de AddSubview(UIView)-aanroep, ware het niet voor de GCHandle
.
Het onbeheerde MyView
-object heeft een GCHandle
die verwijst naar het beheerde object, ook wel een sterke koppelinggenoemd. Het beheerde object bevat een verwijzing naar het Container
-exemplaar. Op zijn beurt krijgt het Container
exemplaar een beheerde verwijzing naar het MyView
-object.
In situaties waarin een ingesloten object een koppeling naar de container houdt, zijn er verschillende opties beschikbaar om de kringverwijzing af te handelen:
- Vermijd de kringreferentie door een zwakke verwijzing naar de container te behouden.
- Roep
Dispose
aan op de objecten. - De cyclus handmatig verbreken door de koppeling naar de container in te stellen op
null
. - Verwijder het ingesloten object handmatig uit de container.
Zwakke verwijzingen gebruiken
Een manier om een cyclus te voorkomen is door een zwakke verwijzing van het kind naar de ouder te gebruiken. De bovenstaande code kan bijvoorbeeld worden weergegeven in het volgende voorbeeld:
class Container : UIView
{
public void Poke()
{
// Call this method to poke this object
}
}
class MyView : UIView
{
WeakReference<Container> _weakParent;
public MyView(Container parent)
{
_weakParent = new WeakReference<Container>(parent);
}
void PokeParent()
{
if (weakParent.TryGetTarget (out var parent))
parent.Poke();
}
}
var container = new Container();
container.AddSubview(new MyView container));
Hier houdt het ingesloten object het bovenliggende object niet in leven. De ouder houdt het kind echter levend via de oproep aan container.AddSubView
.
Dit gebeurt ook in iOS-API's die gebruikmaken van het gedelegeerde of gegevensbron-patroon, waarbij een peer-klasse de implementatie bevat. Als u bijvoorbeeld de eigenschap Delegate of de DataSource instelt in de UITableView-klasse.
In het geval van klassen die uitsluitend worden gemaakt om een protocol te implementeren, bijvoorbeeld het IUITableViewDataSource, wat u kunt doen, is in plaats van een subklasse te maken, u kunt gewoon de interface in de klasse implementeren en de methode overschrijven en de eigenschap DataSource
toewijzen aan this
.
Objecten met sterke verwijzingen verwijderen
Als er een sterke verwijzing bestaat en het moeilijk is om de afhankelijkheid te verwijderen, moet de Dispose
-methode de ouderaanwijzer leegmaken.
Voor containers overschrijft u de methode Dispose
om de ingesloten objecten te verwijderen, zoals wordt weergegeven in het volgende voorbeeld:
class MyContainer : UIView
{
public override void Dispose()
{
// Brute force, remove everything
foreach (var view in Subviews)
{
view.RemoveFromSuperview();
}
base.Dispose();
}
}
Voor een kindobject dat een sterke verwijzing naar zijn ouderobject houdt, wis de verwijzing naar het ouderobject in de Dispose
-implementatie:
class MyChild : UIView
{
MyContainer _container;
public MyChild(MyContainer container)
{
_container = container;
}
public override void Dispose()
{
_container = null;
}
}