Zwiększanie wydajności aplikacji
Niska wydajność aplikacji przedstawia się na wiele sposobów. Może to spowodować, że aplikacja nie odpowiada, może spowodować powolne przewijanie i zmniejszyć żywotność baterii urządzenia. Jednak optymalizacja wydajności wymaga więcej niż tylko zaimplementowania wydajnego kodu. Należy również rozważyć środowisko użytkownika dotyczące wydajności aplikacji. Na przykład zapewnienie, że operacje są wykonywane bez blokowania użytkownikowi wykonywania innych działań, mogą pomóc w ulepszaniu środowiska użytkownika.
Istnieje wiele technik zwiększania wydajności i postrzeganej wydajności w aplikacjach interfejsu użytkownika aplikacji wieloplatformowych platformy .NET (.NET MAUI). Łącznie te techniki mogą znacznie zmniejszyć ilość pracy wykonywanej przez procesor CPU oraz ilość pamięci zużywanej przez aplikację.
Korzystanie z profilera
Podczas tworzenia aplikacji ważne jest, aby podjąć próbę optymalizacji kodu tylko po jego profilowaniu. Profilowanie to technika określania, gdzie optymalizacje kodu będą miały największy wpływ na zmniejszenie problemów z wydajnością. Profiler śledzi użycie pamięci aplikacji i rejestruje czas działania metod w aplikacji. Te dane ułatwiają nawigowanie po ścieżkach wykonywania aplikacji oraz koszt wykonywania kodu, dzięki czemu można odnaleźć najlepsze możliwości optymalizacji.
Aplikacje MAUI platformy .NET można profilować przy użyciu systemów dotnet-trace
Android, iOS i Mac i Windows oraz programu PerfView w systemie Windows. Aby uzyskać więcej informacji, zobacz Profilowanie aplikacji MAUI platformy .NET.
Podczas profilowania aplikacji zalecane są następujące najlepsze rozwiązania:
- Unikaj profilowania aplikacji w symulatorze, ponieważ symulator może zakłócać wydajność aplikacji.
- W idealnym przypadku profilowanie powinno być wykonywane na różnych urządzeniach, ponieważ pomiary wydajności na jednym urządzeniu nie zawsze będą pokazywać charakterystykę wydajności innych urządzeń. Jednak co najmniej profilowanie powinno być wykonywane na urządzeniu, które ma najniższą oczekiwaną specyfikację.
- Zamknij wszystkie inne aplikacje, aby upewnić się, że cały wpływ profilowanej aplikacji jest mierzony, a nie innych aplikacji.
Używanie skompilowanych powiązań
Skompilowane powiązania zwiększają wydajność powiązań danych w aplikacjach .NET MAUI przez rozpoznawanie wyrażeń powiązań w czasie kompilacji, a nie w czasie wykonywania z odbiciem. Kompilowanie wyrażenia powiązania generuje skompilowany kod, który zazwyczaj rozwiązuje powiązanie 8–20 razy szybciej niż użycie powiązania klasycznego. Aby uzyskać więcej informacji, zobacz Skompilowane powiązania.
Zmniejszanie niepotrzebnych powiązań
Nie używaj powiązań dla zawartości, które można łatwo ustawić statycznie. Nie ma żadnych zalet związanych z danymi powiązania, które nie muszą być powiązane, ponieważ powiązania nie są opłacalne. Na przykład ustawienie Button.Text = "Accept"
ma mniejsze obciążenie niż powiązanie Button.Text z właściwością viewmodel string
o wartości "Akceptuj".
Wybieranie prawidłowego układu
Układ, który jest zdolny do wyświetlania wielu elementów podrzędnych, ale ma tylko jedno dziecko, jest marnotrawny. Na przykład w poniższym przykładzie pokazano VerticalStackLayout element z pojedynczym elementem podrzędnym:
<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>
Jest to marne i VerticalStackLayout element powinien zostać usunięty, jak pokazano w poniższym przykładzie:
<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>
Ponadto nie należy podejmować próby odtworzenia wyglądu określonego układu przy użyciu kombinacji innych układów, ponieważ powoduje to niepotrzebne obliczenia układu. Na przykład nie próbuj odtworzyć Grid układu przy użyciu kombinacji HorizontalStackLayout elementów. W poniższym przykładzie przedstawiono przykład tego złego rozwiązania:
<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>
Jest to marnotrawne, ponieważ wykonywane są niepotrzebne obliczenia układu. Zamiast tego żądany układ można lepiej osiągnąć przy użyciu elementu Grid, jak pokazano w poniższym przykładzie:
<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>
Optymalizowanie zasobów obrazu
Obrazy to niektóre z najdroższych zasobów używanych przez aplikacje i są często przechwytywane w wysokiej rozdzielczości. Chociaż powoduje to tworzenie dynamicznych obrazów pełnych szczegółów, aplikacje, które wyświetlają takie obrazy, zwykle wymagają większego użycia procesora CPU do dekodowania obrazu i większej ilości pamięci do przechowywania zdekodowanego obrazu. Dekodowanie obrazu o wysokiej rozdzielczości w pamięci jest marnotrawne, gdy zostanie on przeskalowany w dół do mniejszego rozmiaru na potrzeby wyświetlania. Zamiast tego zmniejsz zużycie procesora CPU i pamięci, tworząc wersje przechowywanych obrazów, które są zbliżone do przewidywanych rozmiarów wyświetlania. Na przykład obraz wyświetlany w widoku listy powinien być najprawdopodobniej niższą rozdzielczością niż obraz wyświetlany na pełnym ekranie.
Ponadto obrazy powinny być tworzone tylko wtedy, gdy jest to wymagane i powinny być zwalniane natychmiast, gdy aplikacja nie będzie już ich wymagała. Jeśli na przykład aplikacja wyświetla obraz, odczytując dane ze strumienia, upewnij się, że strumień jest tworzony tylko wtedy, gdy jest wymagany, i upewnij się, że strumień jest zwalniany, gdy nie jest już wymagany. Można to osiągnąć, tworząc strumień po utworzeniu strony lub gdy Page.Appearing zdarzenie jest uruchamiane, a następnie dysponując strumień po Page.Disappearing uruchomieniu zdarzenia.
Podczas pobierania obrazu do wyświetlania za ImageSource.FromUri(Uri) pomocą metody upewnij się, że pobrany obraz jest buforowany przez odpowiedni czas. Aby uzyskać więcej informacji, zobacz Buforowanie obrazów.
Zmniejszanie rozmiaru drzewa wizualnego
Zmniejszenie liczby elementów na stronie spowoduje szybsze renderowanie strony. Istnieją dwie główne techniki osiągnięcia tego celu. Pierwszym z nich jest ukrycie elementów, które nie są widoczne. Właściwość IsVisible każdego elementu określa, czy element powinien być częścią drzewa wizualnego, czy nie. W związku z tym, jeśli element nie jest widoczny, ponieważ jest ukryty za innymi elementami, usuń element lub ustaw jego IsVisible
właściwość na false
.
Drugą techniką jest usunięcie niepotrzebnych elementów. Na przykład poniżej przedstawiono układ strony zawierający wiele Label elementów:
<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>
Ten sam układ strony można zachować przy użyciu mniejszej liczby elementów, jak pokazano w poniższym przykładzie:
<VerticalStackLayout Padding="20,35,20,20"
Spacing="25">
<Label Text="Hello" />
<Label Text="Welcome to the App!" />
<Label Text="Downloading Data..." />
</VerticalStackLayout>
Zmniejsz rozmiar słownika zasobów aplikacji
Wszystkie zasoby używane w całej aplikacji powinny być przechowywane w słowniku zasobów aplikacji, aby uniknąć duplikowania. Pomoże to zmniejszyć ilość kodu XAML, który musi zostać przeanalizowany w całej aplikacji. W poniższym przykładzie HeadingLabelStyle
przedstawiono zasób, który jest używany dla całej aplikacji, a więc jest zdefiniowany w słowniku zasobów aplikacji:
<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>
Jednak kod XAML specyficzny dla strony nie powinien być uwzględniony w słowniku zasobów aplikacji, ponieważ zasoby będą następnie analizowane podczas uruchamiania aplikacji, a nie wtedy, gdy jest to wymagane przez stronę. Jeśli zasób jest używany przez stronę, która nie jest stroną uruchamiania, powinna zostać umieszczona w słowniku zasobów dla tej strony, co pomaga zmniejszyć liczbę kodu XAML analizowanego podczas uruchamiania aplikacji. W poniższym przykładzie HeadingLabelStyle
pokazano zasób, który znajduje się tylko na jednej stronie, a więc jest zdefiniowany w słowniku zasobów strony:
<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>
Aby uzyskać więcej informacji na temat zasobów aplikacji, zobacz Style apps using XAML (Style apps using XAML).
Zmniejszanie rozmiaru aplikacji
Gdy program .NET MAUI kompiluje aplikację, konsolidator o nazwie ILLink może służyć do zmniejszenia ogólnego rozmiaru aplikacji. IlLink zmniejsza rozmiar, analizując kod pośredni utworzony przez kompilator. Usuwa nieużywane metody, właściwości, pola, zdarzenia, struktury i klasy, aby utworzyć aplikację zawierającą tylko zależności kodu i zestawu niezbędne do uruchomienia aplikacji.
Aby uzyskać więcej informacji na temat konfigurowania zachowania konsolidatora, zobacz Łączenie aplikacji systemu Android, Łączenie aplikacji systemu iOS i Łączenie aplikacji Katalizator dla komputerów Mac.
Skrócenie okresu aktywacji aplikacji
Wszystkie aplikacje mają okres aktywacji, czyli czas między uruchomieniem aplikacji a czasem gotowości aplikacji do użycia. Ten okres aktywacji zapewnia użytkownikom pierwsze wrażenie aplikacji, dlatego ważne jest, aby skrócić okres aktywacji i postrzeganie jej przez użytkownika, aby uzyskać korzystne pierwsze wrażenie aplikacji.
Przed wyświetleniem początkowego interfejsu użytkownika aplikacji powinien zostać wyświetlony ekran powitalny wskazujący użytkownikowi, że aplikacja jest uruchamiana. Jeśli aplikacja nie może szybko wyświetlić swojego początkowego interfejsu użytkownika, ekran powitalny powinien służyć do informowania użytkownika o postępie w okresie aktywacji, aby zapewnić pewność, że aplikacja nie zawiesiła się. To zapewnienie może być paskiem postępu lub podobną kontrolą.
W okresie aktywacji aplikacje wykonują logikę aktywacji, która często obejmuje ładowanie i przetwarzanie zasobów. Okres aktywacji można zmniejszyć, upewniając się, że wymagane zasoby są pakowane w aplikacji, zamiast pobierać zdalnie. Na przykład w niektórych okolicznościach może być to odpowiednie w okresie aktywacji, aby załadować lokalnie przechowywane dane zastępcze. Następnie po wyświetleniu początkowego interfejsu użytkownika i możliwości interakcji użytkownika z aplikacją dane zastępcze mogą być stopniowo zastępowane ze źródła zdalnego. Ponadto logika aktywacji aplikacji powinna wykonywać tylko pracę wymaganą do rozpoczęcia korzystania z aplikacji przez użytkownika. Może to pomóc, jeśli opóźni ładowanie dodatkowych zestawów, ponieważ zestawy są ładowane po raz pierwszy.
Starannie wybierz kontener wstrzykiwania zależności
Kontenery wstrzykiwania zależności wprowadzają dodatkowe ograniczenia wydajności do aplikacji mobilnych. Rejestrowanie i rozpoznawanie typów w kontenerze ma koszt wydajności ze względu na użycie odbicia kontenera do tworzenia każdego typu, zwłaszcza jeśli zależności są odtwarzane dla każdej nawigacji stron w aplikacji. Jeśli istnieje wiele lub głębokie zależności, koszt tworzenia może znacznie wzrosnąć. Ponadto rejestracja typu, która zwykle występuje podczas uruchamiania aplikacji, może mieć zauważalny wpływ na czas uruchamiania, zależny od używanego kontenera. Aby uzyskać więcej informacji na temat wstrzykiwania zależności w aplikacjach .NET MAUI, zobacz Wstrzykiwanie zależności.
Alternatywnie iniekcja zależności może być bardziej wydajna, implementując ją ręcznie przy użyciu fabryk.
Tworzenie aplikacji powłoki
Aplikacje .NET MAUI Shell zapewniają środowisko nawigacji z opiniami na podstawie wysuwanych i kart. Jeśli środowisko użytkownika aplikacji można zaimplementować za pomocą powłoki, warto to zrobić. Aplikacje powłoki pomagają uniknąć słabego środowiska uruchamiania, ponieważ strony są tworzone na żądanie w odpowiedzi na nawigację, a nie podczas uruchamiania aplikacji, co występuje w przypadku aplikacji korzystających z elementu TabbedPage. Aby uzyskać więcej informacji, zobacz Omówienie powłoki.
Optymalizowanie wydajności elementu ListView
W przypadku korzystania z programu ListViewistnieje wiele środowisk użytkownika, które należy zoptymalizować:
- Inicjowanie — interwał czasu rozpoczynający się od utworzenia kontrolki i kończący się, gdy elementy są wyświetlane na ekranie.
- Przewijanie — możliwość przewijania listy i upewnienia się, że interfejs użytkownika nie pozostaje w tyle za gestami dotykowymi.
- Interakcja z dodawaniem, usuwaniem i wybieraniem elementów.
Kontrolka ListView wymaga, aby aplikacja dostarczała dane i szablony komórek. Sposób osiągnięcia tego celu będzie miał duży wpływ na wydajność kontrolki. Aby uzyskać więcej informacji, zobacz Buforowanie danych.
Używanie programowania asynchronicznego
Ogólną szybkość reakcji aplikacji można zwiększyć, a wąskie gardła wydajności są często unikane przy użyciu programowania asynchronicznego. Na platformie .NET wzorzec asynchroniczny oparty na zadaniach (TAP) jest zalecanym wzorcem projektowym dla operacji asynchronicznych. Jednak nieprawidłowe użycie interfejsu TAP może spowodować, że aplikacje nie będą możliwe.
Podstawy
Podczas korzystania z interfejsu TAP należy postępować zgodnie z następującymi ogólnymi wytycznymi:
- Zapoznaj się z cyklem życia zadania, który jest reprezentowany
TaskStatus
przez wyliczenie. Aby uzyskać więcej informacji, zobacz Znaczenie stanu zadania i zadania. Task.WhenAll
Użyj metody , aby asynchronicznie czekać na zakończenie wielu operacji asynchronicznych, a nie pojedynczoawait
serię operacji asynchronicznych. Aby uzyskać więcej informacji, zobacz Task.WhenAll.Task.WhenAny
Użyj metody , aby asynchronicznie poczekać na zakończenie jednej z wielu operacji asynchronicznych. Aby uzyskać więcej informacji, zobacz Task.WhenAny.Task.Delay
Użyj metody , aby utworzyćTask
obiekt, który zakończy się po upływie określonego czasu. Jest to przydatne w przypadku scenariuszy, takich jak sondowanie danych i opóźnianie obsługi danych wejściowych użytkownika dla wstępnie określonego czasu. Aby uzyskać więcej informacji, zobacz Task.Delay.- Wykonaj intensywne operacje synchroniczne procesora CPU w puli wątków przy użyciu
Task.Run
metody . Ta metoda jest skrótem dlaTaskFactory.StartNew
metody z najbardziej optymalnymi argumentami ustawionymi. Aby uzyskać więcej informacji, zobacz Task.Run. - Unikaj próby utworzenia konstruktorów asynchronicznych. Zamiast tego należy użyć zdarzeń cyklu życia lub oddzielnej logiki inicjowania, aby poprawnie
await
zainicjować. Aby uzyskać więcej informacji, zobacz Konstruktory asynchroniczne w blog.stephencleary.com. - Użyj wzorca opóźnionego zadania, aby uniknąć oczekiwania na wykonanie operacji asynchronicznych podczas uruchamiania aplikacji. Aby uzyskać więcej informacji, zobacz AsyncLazy.
- Utwórz otokę zadań dla istniejących operacji asynchronicznych, które nie używają interfejsu TAP, tworząc
TaskCompletionSource<T>
obiekty. Te obiekty uzyskują korzyści zTask
programowania i umożliwiają kontrolowanie okresu istnienia i ukończenia skojarzonego elementuTask
. Aby uzyskać więcej informacji, zobacz Charakter zadaniaCompletionSource. Task
Zwróć obiekt, zamiast zwracać oczekiwanyTask
obiekt, gdy nie ma potrzeby przetwarzania wyniku operacji asynchronicznej. Jest to bardziej wydajne z powodu mniejszego przełączania kontekstu.- Użyj biblioteki przepływów danych biblioteki równoległej zadań (TPL) w scenariuszach, takich jak przetwarzanie danych, gdy staną się dostępne, lub gdy masz wiele operacji, które muszą komunikować się ze sobą asynchronicznie. Aby uzyskać więcej informacji, zobacz Przepływ danych (biblioteka równoległa zadań).
INTERFEJS UŻYTKOWNIKA
Podczas korzystania z interfejsu TAP z kontrolkami interfejsu użytkownika należy postępować zgodnie z następującymi wskazówkami:
Wywołaj asynchroniczną wersję interfejsu API, jeśli jest dostępna. Spowoduje to odblokowanie wątku interfejsu użytkownika, co pomoże ulepszyć środowisko użytkownika w aplikacji.
Zaktualizuj elementy interfejsu użytkownika przy użyciu danych z operacji asynchronicznych w wątku interfejsu użytkownika, aby uniknąć zgłaszania wyjątków. Jednak aktualizacje
ListView.ItemsSource
właściwości będą automatycznie marshalowane do wątku interfejsu użytkownika. Aby uzyskać informacje na temat określania, czy kod jest uruchomiony w wątku interfejsu użytkownika, zobacz Tworzenie wątku w wątku interfejsu użytkownika.Ważne
Wszystkie właściwości kontrolki aktualizowane za pośrednictwem powiązania danych będą automatycznie marshalowane do wątku interfejsu użytkownika.
Obsługa błędów
Podczas korzystania z interfejsu TAP należy postępować zgodnie z następującymi wytycznymi dotyczącymi obsługi błędów:
- Dowiedz się więcej o asynchronicznej obsłudze wyjątków. Nieobsługiwane wyjątki zgłaszane przez kod uruchomiony asynchronicznie są propagowane z powrotem do wątku wywołującego, z wyjątkiem niektórych scenariuszy. Aby uzyskać więcej informacji, zobacz Obsługa wyjątków (biblioteka równoległa zadań).
- Unikaj tworzenia
async void
metod i zamiast tego twórzasync Task
metody. Umożliwiają one łatwiejsze obsługę błędów, komponowanie i możliwość testowania. Wyjątkiem od tych wytycznych są asynchroniczne programy obsługi zdarzeń, które muszą zwrócić wartośćvoid
. Aby uzyskać więcej informacji, zobacz Unikanie Async Void. - Nie mieszaj kodu blokującego i asynchronicznego przez wywołanie
Task.Wait
metod ,Task.Result
lubGetAwaiter().GetResult
, ponieważ mogą one spowodować zakleszczenie. Jeśli jednak te wytyczne muszą zostać naruszone, preferowaną metodą jest wywołanieGetAwaiter().GetResult
metody, ponieważ zachowuje wyjątki zadań. Aby uzyskać więcej informacji, zobacz Async All the Way and Task Exception Handling in .NET 4.5 (Asynchronizuj całą drogę i obsługę wyjątków zadań na platformie .NET 4.5). ConfigureAwait
Użyj metody , jeśli to możliwe, aby utworzyć kod wolny od kontekstu. Kod bez kontekstu ma lepszą wydajność dla aplikacji mobilnych i jest przydatną techniką unikania zakleszczenia podczas pracy z częściowo asynchroniczną bazą kodu. Aby uzyskać więcej informacji, zobacz Konfigurowanie kontekstu.- Użyj zadań kontynuacji dla funkcji, takich jak obsługa wyjątków zgłaszanych przez poprzednią operację asynchroniczną, i anulowanie kontynuacji przed jej uruchomieniem lub uruchomieniem. Aby uzyskać więcej informacji, zobacz Łączenie zadań za pomocą zadań ciągłych.
- Użyj implementacji asynchronicznej ICommand , gdy operacje asynchroniczne są wywoływane z klasy ICommand. Dzięki temu można obsłużyć wszelkie wyjątki w asynchronicznej logice poleceń. Aby uzyskać więcej informacji, zobacz Async Programming: Patterns for Asynchronous MVVM Applications: Commands (Asynchroniczne programowanie: wzorce dla asynchronicznych aplikacji MVVM: polecenia).
Opóźnienie kosztu tworzenia obiektów
Inicjowanie z opóźnieniem może służyć do odroczenia tworzenia obiektu do momentu jego pierwszego użycia. Ta technika jest używana głównie w celu zwiększenia wydajności, uniknięcia obliczeń i zmniejszenia wymagań dotyczących pamięci.
Rozważ użycie inicjowania z opóźnieniem dla obiektów, które są kosztowne do utworzenia w następujących scenariuszach:
- Aplikacja może nie używać obiektu .
- Przed utworzeniem obiektu należy wykonać inne kosztowne operacje.
Klasa Lazy<T>
służy do definiowania typu zainicjowanego z opóźnieniem, jak pokazano w poniższym przykładzie:
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)
{
...
}
Inicjowanie z opóźnieniem następuje przy pierwszym uzyskiwaniu Lazy<T>.Value
dostępu do właściwości. Opakowany typ jest tworzony i zwracany przy pierwszym dostępie oraz przechowywany dla dowolnego przyszłego dostępu.
Aby uzyskać więcej informacji na temat leniwego inicjowania, zobacz Lazy Initialization (Inicjowanie z opóźnieniem).
Wydawanie zasobów IDisposable
Interfejs IDisposable
udostępnia mechanizm wydawania zasobów. Udostępnia metodę Dispose
, która powinna zostać zaimplementowana w celu jawnego wydania zasobów. IDisposable
nie jest destruktorem i powinien być implementowany tylko w następujących okolicznościach:
- Gdy klasa jest właścicielem niezarządzanych zasobów. Typowe niezarządzane zasoby, które wymagają zwolnienia plików, strumieni i połączeń sieciowych.
- Gdy klasa jest właścicielem zarządzanych
IDisposable
zasobów.
Użytkownicy typu mogą następnie wywoływać implementację IDisposable.Dispose
, aby zwolnić zasoby, gdy wystąpienie nie jest już wymagane. Istnieją dwa podejścia do osiągnięcia tego celu:
- Zawijając
IDisposable
obiekt w instrukcjiusing
. - Zawijając wywołanie elementu
IDisposable.Dispose
wtry
/finally
bloku.
Zawijanie obiektu IDisposable w instrukcji using
W poniższym przykładzie pokazano, jak opakowować IDisposable
obiekt w instrukcji using
:
public void ReadText(string filename)
{
string text;
using (StreamReader reader = new StreamReader(filename))
{
text = reader.ReadToEnd();
}
...
}
Klasa StreamReader
implementuje IDisposable
metodę , a using
instrukcja udostępnia wygodną składnię, która wywołuje StreamReader.Dispose
metodę w StreamReader
obiekcie przed wyjściem z zakresu. using
W bloku StreamReader
obiekt jest tylko do odczytu i nie można go ponownie przypisać. Instrukcja using
zapewnia również, że Dispose
metoda jest wywoływana nawet w przypadku wystąpienia wyjątku, ponieważ kompilator implementuje język pośredni (IL) dla try
/finally
bloku.
Zawijanie wywołania do elementu IDisposable.Dispose w bloku try/finally
W poniższym przykładzie pokazano, jak opakowować wywołanie IDisposable.Dispose
w try
/finally
bloku:
public void ReadText(string filename)
{
string text;
StreamReader reader = null;
try
{
reader = new StreamReader(filename);
text = reader.ReadToEnd();
}
finally
{
if (reader != null)
reader.Dispose();
}
...
}
Klasa StreamReader
implementuje IDisposable
metodę , a finally
blok wywołuje metodę StreamReader.Dispose
, aby zwolnić zasób. Aby uzyskać więcej informacji, zobacz IDisposable Interface (Interfejs IDisposable).
Anulowanie subskrypcji zdarzeń
Aby zapobiec wyciekom pamięci, zdarzenia powinny zostać anulowane przed likwidacją obiektu subskrybenta. Dopóki zdarzenie nie zostanie anulowane, pełnomocnik zdarzenia w obiekcie publikowania ma odwołanie do delegata, który hermetyzuje procedurę obsługi zdarzeń subskrybenta. Tak długo, jak obiekt publikowania przechowuje to odwołanie, odzyskiwanie pamięci nie spowoduje odzyskania pamięci obiektu subskrybenta.
W poniższym przykładzie pokazano, jak anulować subskrypcję zdarzenia:
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;
}
}
Klasa Subscriber
anuluje subskrypcję zdarzenia w swojej Dispose
metodzie.
Cykle odwołań mogą również wystąpić w przypadku używania procedur obsługi zdarzeń i składni lambda, ponieważ wyrażenia lambda mogą odwoływać się do obiektów i utrzymywać ich przy życiu. W związku z tym odwołanie do metody anonimowej może być przechowywane w polu i używane do anulowania subskrypcji zdarzenia, jak pokazano w poniższym przykładzie:
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;
}
}
Pole _handler
utrzymuje odwołanie do metody anonimowej i jest używane do subskrypcji zdarzeń i anulowania subskrypcji.
Unikaj silnych odwołań okrągłych w systemach iOS i Mac Catalyst
W niektórych sytuacjach istnieje możliwość utworzenia silnych cykli odwołań, które mogą uniemożliwić obiektom odzyskanie pamięci przez moduł odśmiecenia pamięci. Rozważmy na przykład przypadek, w którym podklasa NSObjectpochodna, taka jak klasa dziedziczona z UIViewklasy , jest dodawana do kontenera pochodnego NSObject
i jest silnie przywoływana z Objective-Cklasy , jak pokazano w poniższym przykładzie:
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));
Po utworzeniu Container
wystąpienia ten kod będzie miał silne odwołanie do Objective-C obiektu w języku C#. MyView
Podobnie wystąpienie będzie mieć silne odwołanie do Objective-C obiektu.
Ponadto wywołanie metody , aby container.AddSubview
zwiększyć liczbę odwołań w wystąpieniu niezarządzanych MyView
. W takim przypadku środowisko uruchomieniowe platformy .NET dla systemu iOS tworzy GCHandle
wystąpienie w celu zachowania MyView
aktywności obiektu w kodzie zarządzanym, ponieważ nie ma żadnej gwarancji, że żadne obiekty zarządzane zachowają do niego odwołanie. Z perspektywy MyView
kodu zarządzanego obiekt zostanie odzyskany po AddSubview(UIView) wywołaniu, gdyby nie obiekt GCHandle
.
Niezarządzany MyView
obiekt będzie miał GCHandle
wskazanie obiektu zarządzanego , znanego jako silny link. Obiekt zarządzany będzie zawierać odwołanie do Container
wystąpienia. Z kolei Container
wystąpienie będzie mieć zarządzane odwołanie do MyView
obiektu.
W sytuacjach, gdy zawarty obiekt przechowuje łącze do kontenera, dostępnych jest kilka opcji obsługi odwołania cyklicznego:
- Unikaj odwołania cyklicznego, utrzymując słabe odwołanie do kontenera.
- Wywołaj metodę
Dispose
na obiektach. - Ręczne przerwanie cyklu przez ustawienie linku do kontenera na
null
. - Ręcznie usuń zawarty obiekt z kontenera.
Używanie słabych odwołań
Jednym ze sposobów zapobiegania cyklu jest użycie słabego odwołania od elementu podrzędnego do elementu nadrzędnego. Na przykład powyższy kod może być pokazany w poniższym przykładzie:
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));
W tym miejscu zawarty obiekt nie będzie utrzymywać elementu nadrzędnego przy życiu. Jednak rodzic utrzymuje dziecko przy życiu przez wywołanie metody container.AddSubView
.
Dzieje się tak również w interfejsach API systemu iOS, które używają wzorca delegata lub źródła danych, gdzie klasa równorzędna zawiera implementację. Na przykład podczas ustawiania Delegate właściwości lub DataSourceUITableView klasy .
W przypadku klas, które są tworzone wyłącznie ze względu na implementację protokołu, na przykład IUITableViewDataSource, co można zrobić, to zamiast tworzyć podklasę, można po prostu zaimplementować interfejs w klasie i zastąpić metodę, a następnie przypisać DataSource
właściwość do this
klasy .
Usuwanie obiektów z silnymi odwołaniami
Jeśli istnieje silne odwołanie i trudno jest usunąć zależność, wyczyść metodę Dispose
wskaźnika nadrzędnego.
W przypadku kontenerów przesłoń metodę Dispose
, aby usunąć zawarte obiekty, jak pokazano w poniższym przykładzie:
class MyContainer : UIView
{
public override void Dispose()
{
// Brute force, remove everything
foreach (var view in Subviews)
{
view.RemoveFromSuperview();
}
base.Dispose();
}
}
W przypadku obiektu podrzędnego, który utrzymuje silne odwołanie do elementu nadrzędnego, wyczyść odwołanie do elementu nadrzędnego w implementacji Dispose
:
class MyChild : UIView
{
MyContainer _container;
public MyChild(MyContainer container)
{
_container = container;
}
public override void Dispose()
{
_container = null;
}
}