Home > database >  Make sure a subscription to an EventHandler is called at Dispose
Make sure a subscription to an EventHandler is called at Dispose

Time:05-02

I'm subscribing to an EventHandler in the constructor and unsubscribing it in the Dispose method. It's important for me to make sure this event is unsubscribed at Dispose, i.e. Dispose is called even if the user forgets calling it.

I added a deconstructor which is supposed to call the Dispose method if the user forgets to, but when I put a breakpoint at it, it doesn't seem like it does.

// Program.cs
// this scope is on purpose to make sure it's disposed
{
    var client = new DeribitClient(DeribitEndpointType.Productive);
}

Console.ReadLine();

// Client.cs
public sealed class Client : IDisposable
{
    private readonly WebSocketClient _client;

    public Client()
    {
        _client = new WebSocketClient("wss://...");
        _client.MessageReceived  = OnMessageReceived;
    }

    public void Dispose()
    {
        _client.MessageReceived -= OnMessageReceived;

        GC.SuppressFinalize(this);
    }

    private void OnMessageReceived(object? sender, MessageReceivedEventArgs e)
    {
    }

    ~Client()
    {
        Dispose();
    }
}

CodePudding user response:

Use a factory

If you really need to have your event unsubscribed, you need Dispose to be called. (Don't use the finalizer because you don't have unmanaged memory.)

To ensure that your clients are disposed (if you can't trust people to use using), then I would advise you to use a factory.

Doing so you will be able to register your factory in a dependency injection container, and when your application closes, the dispose of your factory will be called, so you will be able to dispose there all the clients you have created.

using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;

namespace MyApp
{
    public class Program
    {
        public static void Main(string[] args)
        {
            var hostBuilder = CreateHostBuilder(args);
            using var host = hostBuilder.Build();
            host.Run();
        }

        public static IHostBuilder CreateHostBuilder(string[] args)
        {
            IHostBuilder hostBuilder = Host.CreateDefaultBuilder(args);
            hostBuilder = hostBuilder.ConfigureServices((services) =>
            {
                services.AddSingleton<ClientFactory>();
                services.AddHostedService<MyHostedService>();
            });
            return hostBuilder;
        }
    }


    internal class MyHostedService : BackgroundService
    {
        private readonly ClientFactory _clientFactory;
        private readonly IHostApplicationLifetime _hostApplicationLifetime;

        /// <summary>
        /// Constructor used by dependency injection.
        /// </summary>
        public MyHostedService(ClientFactory clientFactory, IHostApplicationLifetime hostApplicationLifetime)
        {
            _clientFactory = clientFactory;
            _hostApplicationLifetime = hostApplicationLifetime;
        }

        protected override Task ExecuteAsync(CancellationToken stoppingToken)
        {
            var client1 = _clientFactory.GetClient();
            // Oups I forgot to dispose my client

            // Self destruction
            Thread.Sleep(1000);
            _hostApplicationLifetime.StopApplication();
            return Task.CompletedTask;
        }
    }

    internal class ClientFactory : IDisposable
    {
        private List<Client> _clients = new List<Client>();

        public void Dispose()
        {
            foreach (var client in _clients)
            {
                client.Dispose();
            }
            _clients.Clear();
        }

        public Client GetClient()
        {
            var client = new Client();
            _clients.Add(client);
            return client;
        }
    }

    internal class Client : IDisposable
    {
        public void Dispose()
        {
            // Unsubscribe your event here
            // ...
            Console.WriteLine("Client has been disposed");
        }
    }
}
  • Related