Compartir a través de


Mejora del rendimiento de la aplicación

El rendimiento deficiente de la aplicación se presenta de muchas maneras. Puede hacer que una aplicación parezca que no responde, puede provocar un desplazamiento lento y puede reducir la duración de la batería del dispositivo. Sin embargo, la optimización del rendimiento implica más que simplemente implementar código eficaz. También se debe tener en cuenta la experiencia del usuario en el rendimiento de la aplicación. Por ejemplo, asegurarse de que las operaciones se ejecuten sin impedir que el usuario realice otras actividades puede ayudar a mejorar la experiencia del usuario.

Hay muchas técnicas para aumentar el rendimiento y el rendimiento percibido de las aplicaciones .NET MAUI. En conjunto, estas técnicas pueden reducir considerablemente la cantidad de trabajo que realiza una CPU y la cantidad de memoria consumida por una aplicación.

Uso de un generador de perfiles

Al desarrollar una aplicación, es importante intentar optimizar el código una vez que se ha generado el perfil. La generación de perfiles es una técnica para determinar dónde tendrán las optimizaciones de código el mayor efecto para reducir los problemas de rendimiento. El generador de perfiles realiza un seguimiento del uso de memoria de la aplicación y registra el tiempo de ejecución de los métodos en la aplicación. Estos datos ayudan a navegar por las rutas de acceso de ejecución de la aplicación y el costo de ejecución del código, de modo que se puedan detectar las mejores oportunidades de optimización.

Las aplicaciones .NET MAUI se pueden analizar el rendimiento mediante dotnet-trace en Android, iOS, Mac y Windows, y con PerfView en Windows. Para obtener más información, consulte generación de perfiles de aplicaciones .NET MAUI.

Se recomiendan los procedimientos recomendados siguientes al generar perfiles de una aplicación:

  • Evite generar perfiles de una aplicación en un simulador, ya que el simulador puede distorsionar el rendimiento de la aplicación.
  • Lo ideal es que la generación de perfiles se realice en una variedad de dispositivos, ya que tomar medidas de rendimiento en un dispositivo no siempre mostrará las características de rendimiento de otros dispositivos. Sin embargo, como mínimo, la generación de perfiles debe realizarse en un dispositivo que tenga la especificación prevista más baja.
  • Cierre todas las demás aplicaciones para asegurarse de que se mide el impacto total de la aplicación que se está perfilando, y no de las otras aplicaciones.

Utilice enlaces compilados

Los enlaces compilados mejoran el rendimiento del enlace de datos en las aplicaciones MAUI de .NET mediante la resolución de expresiones de enlace en tiempo de compilación, en lugar de en tiempo de ejecución con reflexión. La compilación de una expresión de enlace genera código compilado que normalmente resuelve un enlace de 8 a 20 veces más rápido que usar un enlace clásico. Para obtener más información, consulte vinculaciones compiladas.

Reducción de enlaces innecesarios

No utilice vinculaciones para el contenido que pueda establecerse fácilmente de forma estática. No hay ventaja en vincular datos que no necesitan ser vinculados, ya que las vinculaciones no son eficientes en cuanto a costos. Por ejemplo, establecer Button.Text = "Accept" tiene menos sobrecarga que enlazar Button.Text a la propiedad de modelo de vista string con el valor "Accept".

Elegir el diseño correcto

Un diseño que es capaz de mostrar varios elementos secundarios, pero que solo tiene un solo niño, es desperdiciado. Por ejemplo, el siguiente ejemplo muestra un VerticalStackLayout con un solo hijo.

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

Esto es inútil y se debe quitar el elemento VerticalStackLayout, como se muestra en el ejemplo siguiente:

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

Además, no intente reproducir la apariencia de un diseño específico mediante combinaciones de otros diseños, ya que esto da como resultado cálculos de diseño innecesarios que se realizan. Por ejemplo, no intente reproducir un diseño de Grid mediante una combinación de elementos HorizontalStackLayout. En el ejemplo siguiente se muestra un ejemplo de esta práctica incorrecta:

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

Esto es inútil porque se realizan cálculos de diseño innecesarios. En su lugar, el diseño deseado se puede lograr mejor mediante un Grid, como se muestra en el ejemplo siguiente:

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

Optimización de recursos de imagen

Las imágenes son algunos de los recursos más caros que usan las aplicaciones y a menudo se capturan en resoluciones altas. Aunque esto crea imágenes vibrantes completas de detalle, las aplicaciones que muestran estas imágenes normalmente requieren más uso de CPU para descodificar la imagen y más memoria para almacenar la imagen descodificada. Es ineficiente descodificar una imagen de alta resolución en la memoria cuando se reduce a un tamaño más pequeño para mostrar. En su lugar, reduzca el uso de cpu y la superficie de memoria mediante la creación de versiones de imágenes almacenadas cercanas a los tamaños de visualización previstos. Por ejemplo, una imagen mostrada en una vista de lista probablemente debería ser una resolución menor que una imagen mostrada en pantalla completa.

Además, las imágenes solo deben crearse cuando sea necesario y deben publicarse en cuanto la aplicación ya no las requiera. Por ejemplo, si una aplicación muestra una imagen leyendo sus datos de una secuencia, asegúrese de que la secuencia se crea solo cuando sea necesario y asegúrese de que la secuencia se libere cuando ya no sea necesaria. Esto se puede lograr mediante la creación de la secuencia cuando se crea la página o cuando se desencadena el evento Page.Appearing y, a continuación, se elimina la secuencia cuando se desencadena el evento Page.Disappearing.

Al descargar una imagen para mostrar con el método ImageSource.FromUri(Uri), asegúrese de que la imagen descargada se almacena en caché durante un período de tiempo adecuado. Para obtener más información, consulte almacenamiento en caché de imágenes.

Reducir el número de elementos de una página

Reducir el número de elementos de una página hará que la página se represente más rápido. Existen dos técnicas principales para lograr esto. La primera consiste en ocultar elementos que no son visibles. La propiedad IsVisible de cada elemento determina si el elemento debe estar visible en pantalla. Si un elemento no está visible porque está oculto detrás de otros elementos, quite el elemento o establezca su propiedad IsVisible en false. Establecer la propiedad IsVisible en un elemento en false conserva el elemento en el árbol visual, pero lo excluye de los cálculos de representación y diseño.

La segunda técnica consiste en quitar elementos innecesarios. Por ejemplo, a continuación se muestra un diseño de página que contiene varios elementos 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>

El mismo diseño de página se puede mantener con un recuento de elementos reducido, como se muestra en el ejemplo siguiente:

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

Reducir el tamaño del diccionario de recursos de la aplicación

Los recursos que se usan en toda la aplicación deben almacenarse en el diccionario de recursos de la aplicación para evitar la duplicación. Esto ayudará a reducir la cantidad de XAML que se debe analizar en toda la aplicación. En el ejemplo siguiente se muestra el recurso de HeadingLabelStyle, que se usa en toda la aplicación y, por tanto, se define en el diccionario de recursos de la aplicación:

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

Sin embargo, XAML que es específico de una página no debe incluirse en el diccionario de recursos de la aplicación, ya que los recursos serán analizados en el inicio de la aplicación en lugar de cuando lo exija una página. Si un recurso lo usa una página que no es la página de inicio, debe colocarse en el diccionario de recursos de esa página, lo que ayuda a reducir el XAML que se analiza cuando se inicia la aplicación. En el ejemplo siguiente se muestra el recurso HeadingLabelStyle, que solo se encuentra en una sola página y, por tanto, se define en el diccionario de recursos de la página:

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

Para obtener más información sobre los recursos de la aplicación, consulta Aplicaciones de estilo mediante XAML.

Reducir el tamaño de la aplicación

Cuando .NET MAUI compila la aplicación, se puede usar un enlazador denominado ILLink para reducir el tamaño general de la aplicación. ILLink reduce el tamaño mediante el análisis del código intermedio generado por el compilador. Quita métodos, propiedades, campos, eventos, estructuras y clases sin usar para generar una aplicación que contenga solo las dependencias de código y ensamblado necesarias para ejecutar la aplicación.

Para obtener más información sobre cómo configurar el comportamiento del enlazador, consulte Vinculación de una aplicación android, Vinculación de una aplicación de iOSy Vinculación de una aplicación Mac Catalyst.

Reducir el período de activación de la aplicación

Todas las aplicaciones tienen un período de activación , que es el tiempo entre el momento en que se inicia la aplicación y cuándo la aplicación está lista para usarse. Este período de activación proporciona a los usuarios su primera impresión de la aplicación, por lo que es importante reducir el período de activación y la percepción del usuario, para que obtengan una primera impresión favorable de la aplicación.

Antes de que una aplicación muestre su interfaz de usuario inicial, debe proporcionar una pantalla de presentación para indicar al usuario que se está iniciando la aplicación. Si la aplicación no puede mostrar rápidamente su interfaz de usuario inicial, se debe usar la pantalla de presentación para informar al usuario del progreso durante el período de activación, para ofrecer una garantía de que la aplicación no se ha bloqueado. Esta seguridad podría ser una barra de progreso o un control similar.

Durante el período de activación, las aplicaciones ejecutan la lógica de activación, que a menudo incluye la carga y el procesamiento de recursos. El período de activación se puede reducir asegurándose de que los recursos necesarios se empaquetan dentro de la aplicación, en lugar de recuperarse de forma remota. Por ejemplo, en algunas circunstancias puede ser adecuado durante el período de activación para cargar los datos de marcador de posición almacenados localmente. A continuación, una vez que se muestra la interfaz de usuario inicial y el usuario puede interactuar con la aplicación, los datos del marcador de posición se pueden reemplazar progresivamente desde un origen remoto. Además, la lógica de activación de la aplicación solo debe realizar el trabajo necesario para permitir que el usuario empiece a usar la aplicación. Esto puede ayudar si retrasa la carga de ensamblados adicionales, ya que los ensamblados se cargan la primera vez que se usan.

Elección de un contenedor de inserción de dependencias cuidadosamente

Los contenedores de inserción de dependencias presentan restricciones de rendimiento adicionales en aplicaciones móviles. El registro y resolución de tipos con un contenedor tiene un costo de rendimiento debido al uso de reflexión del contenedor para crear cada tipo, especialmente si se reconstruyen las dependencias para cada navegación de página en la aplicación. Si hay muchas o dependencias profundas, el costo de creación puede aumentar significativamente. Además, el registro de tipos, que suele producirse durante el inicio de la aplicación, puede tener un impacto notable en el tiempo de inicio, dependiendo del contenedor que se esté usando. Para obtener más información sobre la inyección de dependencias en aplicaciones MAUI de .NET, consulte inyección de dependencias.

Como alternativa, la inyección de dependencias se puede hacer más eficiente mediante la implementación manual usando fábricas.

Creación de aplicaciones de Shell

Las aplicaciones de Shell de .NET MAUI proporcionan una experiencia de navegación específica basada en menús desplegables y pestañas. Si la experiencia del usuario de la aplicación se puede implementar con Shell, es beneficioso hacerlo. Las aplicaciones de Shell ayudan a evitar una mala experiencia de inicio, ya que las páginas se crean a petición en respuesta a la navegación en lugar de en el inicio de la aplicación, que se produce con aplicaciones que usan un TabbedPage. Para obtener más información, consulte Descripción general de Shell.

Optimización del rendimiento de ListView

Al usar ListView, hay una serie de experiencias de usuario que se deben optimizar:

  • inicialización: intervalo de tiempo que comienza cuando se crea el control y finaliza cuando se muestran elementos en pantalla.
  • Desplazamiento – la capacidad de desplazarse por la lista y asegurarse de que la interfaz de usuario no se quede atrás respecto a los gestos táctiles.
  • interacción para agregar, eliminar y seleccionar elementos.

El control ListView requiere una aplicación para suministrar datos y plantillas de celdas de datos. Cómo se logra esto tendrá un gran impacto en el rendimiento del control. Para obtener más información, consulte Caché de datos.

Uso de la programación asincrónica

La capacidad de respuesta general de la aplicación se puede mejorar y los cuellos de botella de rendimiento a menudo se evitan mediante la programación asincrónica. En .NET, el patrón asincrónico basado en tareas (TAP) es el patrón de diseño recomendado para las operaciones asincrónicas. Sin embargo, el uso incorrecto de TAP puede dar lugar a aplicaciones no eficaces.

Fundamentos

Las siguientes directrices generales deben seguirse al usar el TAP:

  • Comprenda el ciclo de vida de la tarea, representado por la enumeración TaskStatus. Para obtener más información, consulte El significado de Estado de la tarea y Estado de la tarea.
  • Use el método Task.WhenAll para esperar de forma asincrónica a que finalicen varias operaciones asincrónicas, en lugar de await individualmente una serie de operaciones asincrónicas. Para obtener más información, vea Task.WhenAll.
  • Use el método Task.WhenAny para esperar de forma asincrónica a que finalice una de varias operaciones asincrónicas. Para obtener más información, vea Task.WhenAny.
  • Use el método Task.Delay para generar un objeto Task que finaliza después del tiempo especificado. Esto es útil para escenarios como la recolección de datos y el retraso del procesamiento de la entrada del usuario durante un tiempo predeterminado. Para obtener más información, vea Task.Delay.
  • Ejecute operaciones de CPU sincrónicas intensivas en el grupo de subprocesos con el método Task.Run. Este método es un acceso directo para el método TaskFactory.StartNew, con los argumentos más óptimos establecidos. Para obtener más información, vea Task.Run.
  • Evite intentar crear constructores asincrónicos. En su lugar, use eventos de ciclo de vida o lógica de inicialización independiente para llevar a cabo correctamente cualquier await de inicialización. Para obtener más información, consulte Constructores Asincrónicos en blog.stephencleary.com.
  • Usa el patrón de tarea diferida para evitar esperar la finalización de las operaciones asincrónicas durante el inicio de la aplicación. Para obtener más información, vea AsyncLazy.
  • Cree un contenedor de tareas para las operaciones asincrónicas existentes que no utilizan el patrón TAP, creando objetos TaskCompletionSource<T>. Estos objetos obtienen las ventajas de la programabilidad de Task y le permiten a usted controlar la duración y la finalización de la Taskasociada. Para obtener más información, vea The Nature of TaskCompletionSource.
  • Devuelve un objeto Task, en lugar de devolver un objeto Task esperado, cuando no es necesario procesar el resultado de una operación asincrónica. Esto es más eficaz debido a que se está realizando un cambio de contexto menor.
  • Use la biblioteca de flujos de datos de la Biblioteca paralela de tareas (TPL) en escenarios como el procesamiento de datos a medida que esté disponible o cuando tenga varias operaciones que se deben comunicar entre sí de forma asincrónica. Para obtener más información, vea Flujo de Datos (Biblioteca de Paralelismo de Tareas).

IU

Se deben seguir las directrices siguientes al usar el TAP con Controles de Interfaz de Usuario:

  • Llame a una versión asincrónica de una API, si está disponible. Esto mantendrá desbloqueado el hilo de la interfaz de usuario, lo que ayudará a mejorar la experiencia del usuario en la aplicación.

  • Actualice los elementos de la interfaz de usuario con datos de operaciones asincrónicas en el subproceso de la interfaz de usuario para evitar que se produzcan excepciones. Sin embargo, las actualizaciones de la propiedad ListView.ItemsSource se serializarán automáticamente en el subproceso de la interfaz de usuario. Para obtener información sobre cómo determinar si el código se ejecuta en el subproceso de la interfaz de usuario, consulte Crear un subproceso en el subproceso de interfaz de usuario.

    Importante

    Las propiedades de los controles que se actualizan a través del enlace de datos se transferirán automáticamente al hilo de la interfaz de usuario.

Control de errores

Se deben seguir las siguientes directrices de control de errores al usar tap:

  • Obtenga información sobre el control asincrónico de excepciones. Las excepciones no controladas producidas por código que se ejecuta de forma asincrónica se propagan de nuevo al subproceso que realiza la llamada, excepto en determinados escenarios. Para obtener más información, vea Control de excepciones (Biblioteca paralela de tareas).
  • Evite crear métodos async void y, en su lugar, cree métodos async Task. Esto facilita el control de errores, la capacidad de redacción y la capacidad de prueba. La excepción a esta guía es los controladores de eventos asincrónicos, que deben devolver void. Para obtener más información, vea Evitar Async Void.
  • No mezcle el bloqueo y el código asincrónico llamando a los métodos Task.Wait, Task.Resulto GetAwaiter().GetResult, ya que pueden provocar interbloqueos. Sin embargo, si se debe infringir esta guía, el enfoque preferido es llamar al método GetAwaiter().GetResult porque conserva las excepciones de tarea. Para obtener más información, vea Async All the Way y Task Exception Handling in .NET 4.5.
  • Use el método ConfigureAwait siempre que sea posible, para crear código sin contexto. El código sin contexto tiene un mejor rendimiento para las aplicaciones móviles y es una técnica útil para evitar interbloqueo al trabajar con un código base parcialmente asincrónico. Para obtener más información, vea Configure Context.
  • Utilice tareas de continuación para funcionalidades tales como manejar las excepciones arrojadas por la operación asincrónica anterior y cancelar una continuación antes de que se inicie o mientras se ejecuta. Para obtener más información, vea Encadenamiento de tareas mediante tareas continuas.
  • Use una implementación de ICommand asincrónica cuando se invocan operaciones asincrónicas desde el ICommand. Esto garantiza que se puedan controlar todas las excepciones de la lógica de comandos asincrónica. Para obtener más información, vea programación asincrónica: patrones para aplicaciones asincrónicas de MVVM: comandos.

Retrasar el costo de crear objetos

La inicialización diferida se puede usar para aplazar la creación de un objeto hasta que se use por primera vez. Esta técnica se usa principalmente para mejorar el rendimiento, evitar el cálculo y reducir los requisitos de memoria.

Considere la posibilidad de usar la inicialización diferida para los objetos que son costosos de crear en los escenarios siguientes:

  • Es posible que la aplicación no use el objeto .
  • Otras operaciones costosas deben completarse antes de crear el objeto.

La clase Lazy<T> se utiliza para definir un tipo de inicialización perezosa, como se muestra en el siguiente ejemplo:

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)
{
    ...
}

La inicialización perezosa se produce la primera vez que se accede a la propiedad Lazy<T>.Value. El tipo envuelto se crea y devuelve en el primer acceso, y se almacena para cualquier acceso futuro.

Para obtener más información sobre la inicialización diferida, vea Inicialización diferida.

Liberar recursos IDisposables

La interfaz IDisposable proporciona un mecanismo para liberar recursos. Proporciona un método Dispose que se debe implementar para liberar explícitamente los recursos. IDisposable no es un destructor y solo debe implementarse en las siguientes circunstancias:

  • Cuando la clase posee recursos no administrados. Los recursos no administrados típicos que requieren liberar incluyen archivos, secuencias y conexiones de red.
  • Cuando la clase posee recursos de IDisposable administrados.

A continuación, los consumidores de tipos pueden llamar a la implementación de IDisposable.Dispose para liberar recursos cuando la instancia ya no sea necesaria. Hay dos enfoques para lograr esto:

  • Al encapsular el objeto IDisposable en una instrucción using.
  • Al envolver la llamada a IDisposable.Dispose en un bloque de try/finally.

Envuelva el objeto IDisposable en una instrucción using

En el ejemplo siguiente se muestra cómo encapsular un objeto IDisposable en una instrucción using:

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

La clase StreamReader implementa IDisposabley la instrucción using proporciona una sintaxis cómoda que llama al método StreamReader.Dispose en el objeto StreamReader antes de salir del ámbito. Dentro del bloque using, el objeto StreamReader es de solo lectura y no se puede reasignar. La instrucción using también garantiza que se llame al método Dispose incluso si se produce una excepción, ya que el compilador implementa el lenguaje intermedio (IL) para un bloque de try/finally.

Encapsular la llamada a IDisposable.Dispose en un bloque try/finally

En el siguiente ejemplo se muestra cómo envolver la llamada a IDisposable.Dispose dentro de un bloque try/finally.

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

La clase StreamReader implementa IDisposabley el bloque finally llama al método StreamReader.Dispose para liberar el recurso. Para obtener más información, vea interfaz IDisposable.

Cancelar la suscripción de eventos

Para evitar pérdidas de memoria, los eventos deben desuscribirse antes de que se elimine el objeto suscriptor. Hasta que se cancela la suscripción al evento, el delegado del evento en el objeto publicador tiene una referencia al delegado que encapsula el controlador de eventos del suscriptor. Siempre que el objeto de publicación mantenga esta referencia, la recolección de basura no reclamará la memoria del objeto suscriptor.

En el ejemplo siguiente se muestra cómo cancelar la suscripción a un evento:

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

La clase Subscriber cancela la suscripción del evento en su método Dispose.

Los ciclos de referencia también pueden producirse al usar controladores de eventos y sintaxis lambda, ya que las expresiones lambda pueden hacer referencia a objetos y mantener activos los objetos. Por lo tanto, una referencia al método anónimo se puede almacenar en un campo y usarse para cancelar la suscripción del evento, como se muestra en el ejemplo siguiente:

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

El campo _handler mantiene la referencia al método anónimo y se usa para la suscripción de eventos y cancelación de suscripción.

Evitar referencias circulares fuertes en iOS y Mac Catalyst

En algunas situaciones, es posible crear ciclos de referencia seguros que podrían impedir que los objetos tengan su memoria reclamada por el recolector de elementos no utilizados. Por ejemplo, considere el caso en el que una subclase derivada de NSObject, como una clase que hereda de UIView, se agrega a un contenedor derivado de NSObjecty se hace referencia fuertemente desde Objective-C, como se muestra en el ejemplo siguiente:

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

Cuando este código crea la instancia de Container, el objeto de C# tendrá una referencia segura a un objeto Objective-C. Del mismo modo, la instancia de MyView también tendrá una referencia segura a un objeto Objective-C.

Además, la llamada a container.AddSubview aumentará el conteo de referencias en la instancia no administrada de MyView. Cuando esto sucede, el entorno de ejecución de .NET para iOS crea una instancia de GCHandle para mantener activo el objeto MyView en código administrado, ya que no hay ninguna garantía de que los objetos administrados conserven una referencia a él. Desde una perspectiva de código administrado, el objeto MyView se recuperaría después de la llamada AddSubview(UIView) si no fuera por el GCHandle.

El objeto MyView no administrado tendrá un GCHandle que apunta al objeto administrado, conocido como vínculo seguro. El objeto administrado contendrá una referencia a la instancia de Container. A su vez, la instancia de Container tendrá una referencia administrada al objeto MyView.

En circunstancias en las que un objeto contenido mantiene un vínculo a su contenedor, hay varias opciones disponibles para tratar con la referencia circular:

  • Evite la referencia circular manteniendo una referencia débil al contenedor.
  • Llame a Dispose en los objetos .
  • Interrumpa manualmente el ciclo estableciendo el vínculo al contenedor en null.
  • Quite manualmente el objeto contenido del contenedor.

Uso de referencias débiles

Una manera de evitar un ciclo es usar una referencia débil del hijo al padre. Por ejemplo, el código anterior podría ser como se muestra en el ejemplo siguiente:

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

Aquí, el objeto contenido no mantendrá vivo al padre. Sin embargo, el padre mantiene al hijo vivo a través de la llamada a container.AddSubView.

Esto también ocurre en las APIs de iOS que utilizan el patrón de delegado o fuente de datos, donde una clase al mismo nivel contiene la implementación. Por ejemplo, al establecer la propiedad Delegate o el DataSource en la clase UITableView.

En el caso de las clases que se crean exclusivamente para implementar un protocolo, por ejemplo, el IUITableViewDataSource, lo que puede hacer es en lugar de crear una subclase, simplemente puede implementar la interfaz en la clase e invalidar el método y asignar la propiedad DataSource a this.

Eliminar objetos con referencias fuertes

Si existe una referencia segura y es difícil quitar la dependencia, haga que un método Dispose borre el puntero primario.

En el caso de los contenedores, invalide el método Dispose para quitar los objetos contenidos, como se muestra en el ejemplo siguiente:

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

Para un objeto secundario que mantiene una referencia fuerte a su elemento primario, borre la referencia al elemento primario en la implementación de Dispose:

class MyChild : UIView
{
    MyContainer _container;

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

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