共用方式為


改善應用程式效能

應用程式效能不佳以多種方式呈現。 它可以讓應用程式看起來沒有回應,可能會導致捲動緩慢,並可以減少裝置的電池使用時間。 不過,優化效能不僅僅是實作有效率的程序代碼。 也必須考慮應用程式效能的用戶體驗。 例如,確保作業執行而不封鎖使用者執行其他活動,有助於改善用戶體驗。

.NET MAUI 應用程式的效能和感知效能有許多提升技術。 這些技術可以大幅減少 CPU 所執行的工作量,以及應用程式所耗用的記憶體數量。

使用分析工具

開發應用程式時,請務必在分析程式碼之後,只嘗試優化程序代碼。 程式碼分析是一種技術,可以判斷程式代碼優化在改善效能問題方面效果最佳。 分析工具會追蹤應用程式的記憶體使用量,並記錄應用程式中方法的運行時間。 此數據有助於瀏覽應用程式的執行路徑,以及程式碼的執行成本,以便探索優化的最佳機會。

您可以在 Android、iOS 和 Mac 和 Windows 上使用 dotnet-trace,以及 Windows 上的 PerfView 來分析 .NET MAUI 應用程式。 如需詳細資訊,請參閱 分析 .NET MAUI 應用程式

分析應用程式時,建議使用下列最佳做法:

  • 避免在模擬器中分析應用程式,因為模擬器可能會扭曲應用程式效能。
  • 在理想情況下,分析應該在各種裝置上執行,因為一個裝置上的效能測量不一定會顯示其他裝置的效能特性。 不過,至少應該在具有最低預期規格的裝置上執行分析。
  • 關閉所有其他應用程式,以確保要分析之應用程式的完整影響正在測量,而不是其他應用程式。

使用編譯的系結

編譯式繫結透過在編譯時期解析繫結運算式,而非在運行時期使用反射,改善了 .NET MAUI 應用程式中的數據繫結效能。 編譯系結表達式會產生編譯的程序代碼,通常會比使用傳統系結快 8-20 倍解析系結。 如需詳細資訊,請參閱 編譯綁定

減少不必要的系結

請勿將系結用於可以輕鬆地以靜態方式設定的內容。 因為系結沒有成本效益,所以沒有必要系結不需要系結的數據。 例如,設定 Button.Text = "Accept" 的開銷較少,比起系結 Button.Text 至具有值『Accept』的 viewmodel string 屬性。

選擇正確的版面配置

能夠顯示多個子系但只有單一子系的版面配置是浪費的。 例如,下列範例顯示具有單一子系的 VerticalStackLayout

<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>

這是浪費的,應該移除 VerticalStackLayout 元素,如下列範例所示:

<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>

此外,請勿嘗試使用其他版面配置的組合來重現特定版面配置的外觀,因為這樣會導致執行不必要的版面配置計算。 例如,請勿嘗試使用 HorizontalStackLayout 元素的組合來重現 Grid 版面配置。 下列範例示範此不良做法的範例:

<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>

這樣做是浪費的,因為會執行不必要的版面佈局計算。 相反地,理想的版面配置可以使用 Grid來達成,如下列範例所示:

<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>

優化映像資源

影像是應用程式使用的一些最昂貴的資源,而且通常會以高解析度擷取。 雖然這會建立充滿詳細數據的充滿活力的影像,但顯示這類影像的應用程式通常需要更多 CPU 使用量來譯碼影像和更多記憶體來儲存已譯碼的影像。 當要將高解析度影像縮小來顯示時,在記憶體中進行解碼是很浪費的。 相反地,建立接近預測顯示大小的預存映像版本,以減少CPU使用量和記憶體使用量。 例如,在清單檢視中顯示的影像,最可能比全屏幕顯示影像的解析度更低。

此外,只有在需要時才會建立映像,而且應該在應用程式不再需要時立即釋出映像。 例如,如果應用程式透過從資料流讀取來顯示影像,請確保僅在需要時才建立資料流,並在不再需要時釋放資料流。 當頁面建立或 Page.Appearing 事件觸發時建立數據流,然後在 Page.Disappearing 事件觸發時處理數據流,即可達成此目的。

使用 ImageSource.FromUri(Uri) 方法下載要顯示的影像時,請確定下載的影像會緩存一段適當的時間。 如需詳細資訊,請參閱 影像快取

減少頁面上的項目數目

減少頁面上的元素數量會使頁面渲染更快。 有兩個主要技術可以達成此目標。 第一個是隱藏不可見的元素。 每個元素的 IsVisible 屬性會決定是否應該在螢幕上顯示元素。 如果元素因為隱藏在其他元素後面而看不到,請移除該元素,或將其 IsVisible 屬性設定為 false。 將元素上的 IsVisible 屬性設定為 false 會保留該元素在視覺樹狀結構中,但會將其排除在渲染和版面配置計算之外。

第二個技巧是移除不必要的元素。 例如,下列顯示包含多個 Label 元素的頁面版面配置:

<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>

相同的頁面布局可以透過較少的元素數量來維護,如下列範例所示:

<VerticalStackLayout Padding="20,35,20,20"
                     Spacing="25">
    <Label Text="Hello" />
    <Label Text="Welcome to the App!" />
    <Label Text="Downloading Data..." />
</VerticalStackLayout>

減少應用程式資源字典大小

在應用程式中使用的任何資源都應該儲存在應用程式的資源字典中,以避免重複。 這有助於減少整個應用程式必須剖析的 XAML 數量。 下列範例顯示 HeadingLabelStyle 資源,其使用範圍為全應用程式,因此定義於應用程式的資源字典中:

<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 不應該包含在應用程式的資源字典中,因為這樣資源會在應用程式啟動時就被剖析,而不會在頁面需要時才剖析。 如果資源是由不是啟動頁面的其他頁面使用,它應該放在該頁面的資源字典中,因此有助於減少應用程式啟動時解析的 XAML。 下列範例顯示 HeadingLabelStyle 資源,該資源只位於單一頁面上,因此定義在頁面的資源字典中:

<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>

如需有關應用程式資源的更多資訊,請參閱 使用 XAML 樣式應用程式

減少應用程式的大小

當 .NET MAUI 建置您的應用程式時,可以使用稱為 ILLink 的連結器來減少應用程式的整體大小。 ILLink藉由分析編譯程式所產生的中繼程式代碼來減少大小。 它會移除未使用的方法、屬性、欄位、事件、結構及類別,以產生只包含執行應用程式所需的程式代碼和元件相依性的應用程式。

如需設定連結器行為的詳細資訊,請參閱 連結 Android 應用程式連結 iOS 應用程式,以及 連結 Mac Catalyst 應用程式

減少應用程式啟用期間

所有應用程式都有 啟用期間,這表示應用程式從啟動到可以使用之間的時間。 此啟用期間為使用者提供了對應用程式的第一印象,因此請務必減少啟用期間和使用者對其的看法,以便他們獲得對應用程式的有利第一印象。

在應用程式顯示其初始UI之前,它應該提供啟動顯示畫面,向使用者指出應用程式正在啟動。 如果應用程式無法快速顯示其初始用戶介面,則應該使用啟動顯示畫面來通知使用者在啟用期間的進度,提供應用程式沒有當機的保証。 這種安慰可能是進度列或類似的控制元素。

在啟用期間,應用程式會執行啟用邏輯,這通常包含資源的載入和處理。 藉由確保必要的資源封裝在應用程式內,而不是從遠端擷取,即可減少啟用期間。 例如,在某些情況下,在啟動期間可能會適合載入本機儲存的佔位元資料。 然後,一旦顯示初始使用者介面,且用戶能夠與應用程式互動,就可以從遠端來源逐漸取代佔位數據。 此外,應用程式的啟用邏輯應該只會執行讓用戶開始使用應用程式所需的工作。 首次使用元件時載入,這可以幫助延遲載入其他元件。

仔細選擇相依性插入容器

相依性插入容器會將額外的效能限制引入行動應用程式。 使用容器註冊和解析類型具有性能成本,因為容器使用反射機制來產生每個類型,特別是在應用程式中的每次頁面導航時重新建構相依性。 如果有許多或深層相依性,建立成本可能會大幅增加。 此外,通常發生在應用程式啟動期間的類型註冊可能會對啟動時間造成明顯影響,視所使用的容器而定。 如需 .NET MAUI 應用程式中相依性插入的詳細資訊,請參閱 相依性插入

或者,可以透過使用工廠手動實作的方法來提升相依性注入的效能。

建立Shell應用程序

.NET MAUI Shell 應用程式會根據飛出視窗和索引標籤提供有意見的瀏覽體驗。 如果您的應用程式用戶體驗可以使用Shell來實作,這樣做是有益的。 殼層應用程式有助於避免不佳的啟動體驗,因為頁面會根據流覽需求即時建立,而不是在應用程式啟動時就建立,這些殼層應用程式使用的是 TabbedPage。 如需詳細資訊,請參閱 Shell 概觀

優化 ListView 效能

使用 ListView時,應該優化許多用戶體驗:

  • 初始化 – 從控件建立開始的時間間隔,以及在畫面上顯示項目時結束。
  • 滾動 – 滾動清單的能力,並確保使用者介面不落後於觸控手勢。
  • 新增、刪除和選取項目的互動

ListView 控制元件需要應用程式提供資料和儲存格範本。 如何達成此目的,會對控件的效能產生很大影響。 如需詳細資訊,請參閱 快取資料

使用異步程序設計

您可以藉由使用異步程式設計來增強應用程式的整體回應性,並經常避免效能瓶頸。 在 .NET 中,工作型異步模式 (TAP) 是異步操作的建議設計模式。 不過,對 TAP 的使用不正確可能會導致應用程式表現不佳。

基礎

使用 TAP 時,應遵循下列一般指導方針:

  • 瞭解工作生命週期,其是由 TaskStatus 列舉來表示的。 如需詳細資訊,請參閱 任務狀態的意義任務狀態
  • 使用 Task.WhenAll 方法來異步等候多個異步操作完成,而不是個別 await 一系列異步操作。 如需詳細資訊,請參閱 Task.WhenAll
  • 使用 Task.WhenAny 方法,以異步方式等候其中一個異步操作完成。 如需詳細資訊,請參閱 Task.WhenAny
  • 使用 Task.Delay 方法來產生在指定時間之後完成的 Task 物件。 這適用於輪詢數據,以及延遲處理預先決定時間的使用者輸入等案例。 如需詳細資訊,請參閱 Task.Delay
  • 使用 Task.Run 方法,在線程集區上執行密集的同步 CPU 作業。 此方法是 TaskFactory.StartNew 方法的快捷方式,其中已設定最佳自變數。 如需詳細資訊,請參閱 Task.Run
  • 避免嘗試建立異步建構函式。 請改用生命週期事件或個別的初始化邏輯,正確地 await 任何初始化。 如需詳細資訊,請參閱 blog.stephencleary.com 上的 異步建構函式
  • 使用惰性任務模式,以避免在應用程式啟動期間等待異步操作完成。 如需詳細資訊,請參閱 AsyncLazy
  • 藉由建立 TaskCompletionSource<T> 物件,為不使用 TAP 的現有異步操作建立工作包裝函式。 這些物件獲得了 Task 可程式設計的優點,並讓您能夠控制相關聯的 Task的存留期和完成。 如需更多資訊,請參閱 TaskCompletionSource 的特性
  • 當不需要處理異步操作的結果時,傳回 Task 物件,而不是傳回等候的 Task 物件。 由於頻繁執行的內容切換較少,因此性能更佳。
  • 使用工作平行程式庫(TPL)資料流程程式庫,例如在資料可供使用時處理資料,或者當您有多個作業必須以非同步方式進行通訊時。 如需詳細資訊,請參閱 資料流 (工作平行連結庫)

用戶介面

使用 TAP 搭配 UI 控制件時,應該遵循下列指導方針:

  • 如果有的話,請呼叫 API 的異步版本。 這會將UI線程解除封鎖,這有助於改善應用程式的用戶體驗。

  • 在 UI 執行緒中以非同步操作中取得的資料更新 UI 元素,以避免拋出例外。 不過,ListView.ItemsSource 屬性的更新會自動封送處理至 UI 執行緒。 如需判斷程式代碼是否在 UI 線程上執行的資訊,請參閱 在 UI 線程上建立線程

    重要

    透過資料繫結更新的任何控制項屬性都會自動傳送至使用者介面執行緒。

錯誤處理

使用 TAP 時,應該遵循下列錯誤處理指導方針:

  • 瞭解異步異常處理。 執行異步的程式代碼所擲回的未處理例外狀況會傳播回呼叫線程,但在某些情況下除外。 如需詳細資訊,請參閱 例外狀況處理(工作平行連結庫)
  • 請避免建立 async void 方法,而是改為建立 async Task 方法。 這些可讓錯誤處理、可組合性和可測試性更容易。 此指導方針的例外狀況是異步事件處理程式,必須傳回 void。 如需詳細資訊,請參閱 避免異步 Void
  • 請勿藉由呼叫 Task.WaitTask.ResultGetAwaiter().GetResult 方法來混合封鎖和異步程式代碼,因為它們可能會導致死結發生。 不過,如果必須違反此指導方針,慣用的方法就是呼叫 GetAwaiter().GetResult 方法,因為它會保留工作例外狀況。 如需詳細資訊,請參閱 全程異步 和在 .NET 4.5中的 工作例外狀況處理。
  • 盡可能使用 ConfigureAwait 方法來建立無內容程序代碼。 上下文無關程式碼對於行動應用程式有較佳的效能,而且是在處理部分非同步程式碼基底時避免死結的實用技術。 如需詳細資訊,請參閱 上下文設定
  • 使用 接續工作 處理上一個異步操作擲回的例外狀況,以及在啟動之前或正在執行時取消接續等功能。 如需詳細資訊,請參閱 使用連續工作鏈結工作
  • 當從 ICommand呼叫非同步操作時,請使用非同步 ICommand 實作。 這可確保可以處理異步命令邏輯中的任何例外狀況。 如需詳細資訊,請參閱 異步程式設計:異步 MVVM 應用程式的模式:命令

延後建立物件的成本

延遲初始化可用來延遲建立物件,直到第一次使用為止。 這項技術主要用於改善效能、避免計算並減少記憶體需求。

請考慮針對在下列情境中創建成本高昂的物件使用延遲初始化:

  • 應用程式可能不會使用物件。
  • 建立物件之前,必須先完成其他昂貴的作業。

Lazy<T> 類別可用來定義延遲初始化的類型,如下列範例所示:

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<T>.Value 屬性時,就會發生延遲初始化。 包裝的類型會在首次存取時被建立並傳回,之後將儲存以供未來任何存取使用。

如需延遲初始化的詳細資訊,請參閱 延遲初始化

發行 IDisposable 資源

IDisposable 介面提供釋放資源的機制。 應該實作其提供的 Dispose 方法以明確釋放資源。 IDisposable 不是解構函式,而且應該只在下列情況下實作:

  • 當類別擁有非受控資源時。 需要釋放的典型非受控資源包括檔案、數據流和網路連線。
  • 當類別擁有管理的 IDisposable 資源時。

然後,類型使用者可以呼叫 IDisposable.Dispose 實作,在實例不再需要時釋放資源。 有兩種方法可以達成此目標:

  • IDisposable 對象包裝在 using 語句中。
  • 將呼叫包裝在 IDisposable.Dispose 中的 try/finally 區塊。

在using語句中包裝IDisposable物件

下列範例示範如何在 using 語句中包裝 IDisposable 物件:

public void ReadText(string filename)
{
    string text;
    using (StreamReader reader = new StreamReader(filename))
    {
        text = reader.ReadToEnd();
    }
    ...
}

StreamReader 類別會實作 IDisposable,而 using 語句提供了一種方便的語法,可以在 StreamReader 物件即將超出作用域之前呼叫 StreamReader.Dispose 方法。 在 using 區塊中,StreamReader 物件是唯讀的,無法重新指派。 using 語句也可確保即使發生例外狀況,也會呼叫 Dispose 方法,因為編譯程式會實作 try/finally 區塊的中繼語言 (IL)。

在 try/finally 區塊中將對 IDisposable.Dispose 的呼叫包裹起來

下列範例示範如何在 try/finally 區塊中包裝對 IDisposable.Dispose 的呼叫:

public void ReadText(string filename)
{
    string text;
    StreamReader reader = null;
    try
    {
        reader = new StreamReader(filename);
        text = reader.ReadToEnd();
    }
    finally
    {
        if (reader != null)
            reader.Dispose();
    }
    ...
}

StreamReader 類別會實作 IDisposable,而 finally 區塊會呼叫 StreamReader.Dispose 方法來釋放資源。 如需詳細資訊,請參閱 IDisposable 介面

取消訂閱活動通知

若要防止記憶體流失,應該在處置訂閱者物件之前取消訂閱事件。 在取消訂閱事件之前,發行物件中事件的委派具有封裝訂閱者事件處理程式之委派的參考。 只要發佈物件保存此參考,垃圾收集器就不會回收訂閱者物件的記憶體。

下列範例示範如何取消事件訂閱:

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;
    }
}

Subscriber 類別會在其 Dispose 方法中取消訂閱事件。

使用事件處理程式和 Lambda 語法時,也會發生參考迴圈,因為 Lambda 表達式可以參考並讓物件保持運作。 因此,匿名方法的參考可以儲存在字段中,並用來取消訂閱事件,如下列範例所示:

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;
    }
}

[_handler] 字段會保持對匿名方法的參考,並用於事件訂閱及取消訂閱。

避免 iOS 和 Mac Catalyst 上的強循環參考

在某些情況下,可能會建立強式參考迴圈,這可能會阻止物件的記憶體被垃圾回收器回收。 例如,假設衍生 NSObject子類別,例如繼承自 UIView的類別,會新增至 NSObject衍生容器,而且從 Objective-C強式參考,如下列範例所示:

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));

當此程式碼建立 Container 實例時,C# 物件會有 Objective-C 物件的強式參考。 同樣地,MyView 實例也會對 Objective-C 物件有強式參考。

此外,呼叫 container.AddSubview 會增加非管理 MyView 實例的引用計數。 發生這種情況時,iOS 平台上 .NET 的執行時會建立 GCHandle 實例,以便在受控代碼中保持 MyView 對象的存活,因為不保證任何受控對象都會保留其引用。 從 Managed 程式代碼的觀點來看,本來在呼叫 AddSubview(UIView) 之後,MyView 物件會被回收,如果不是因為 GCHandle的話。

未管理 MyView 物件將擁有指向管理物件的 GCHandle,這被稱為 強連結。 受管理的物件將包含 Container 實例的參考。 接著,Container 實例會有對 MyView 物件的管理參考。

在被包含的物件保留其容器鏈接的情況下,有數個選項可用來處理循環參照:

  • 藉由保留容器的弱參考,以避免循環參考。
  • 在物件上呼叫 Dispose
  • 將容器的連結設定為 null,以手動中斷迴圈。
  • 手動從容器中移除內含物件。

使用弱引用

防止迴圈的其中一個方法是使用子系對父系的弱式參考。 例如,上述程式代碼可能如下列範例所示:

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));

在此,包含的物件不會維持父物件的存活。 不過,父系會透過呼叫 container.AddSubView讓子系保持運作。

這也會發生在使用委派或數據源模式的 iOS API 中,其中對等類別包含 實作。 例如,在設定 UITableView 類別中的 Delegate 屬性或 DataSource 時。

在純粹為了實作通訊協定而建立的類別案例中,例如,IUITableViewDataSource,您可以執行的動作是,而不是建立子類別,您可以直接在 類別中實作 介面並覆寫 方法,並將 DataSource 屬性指派給 this

處置具有強式參考的物件

如果強式參考存在且難以移除相依性,請讓 Dispose 方法清除父指標。

針對容器,請覆寫 Dispose 方法來刪除已包含的物件,正如以下範例所示:

class MyContainer : UIView
{
    public override void Dispose()
    {
        // Brute force, remove everything
        foreach (var view in Subviews)
        {
              view.RemoveFromSuperview();
        }
        base.Dispose();
    }
}

對於保留其父物件強引用的子物件,請在 Dispose 實作中清除對父物件的引用。

class MyChild : UIView
{
    MyContainer _container;

    public MyChild(MyContainer container)
    {
        _container = container;
    }

    public override void Dispose()
    {
        _container = null;
    }
}