Tricky IDisposable Issue

2019-05-07 19:00发布

问题:

I'm trying to abstract/encapsulate the following code so all client calls don't need to repeat this code. For example, this is a call, from a view model (MVVM) to a WCF service:

using (var channelFactory = new WcfChannelFactory<IPrestoService>(new NetTcpBinding()))
{
    var endpointAddress = ConfigurationManager.AppSettings["prestoServiceAddress"];
    IPrestoService prestoService = channelFactory.CreateChannel(new EndpointAddress(endpointAddress));    
    this.Applications = new ObservableCollection<Application>(prestoService.GetAllApplications().ToList());
}

My original attempt at refactoring was to do this:

public static class PrestoWcf
{
    public static IPrestoService PrestoService
    {
        get
        {
            using (var channelFactory = new WcfChannelFactory<IPrestoService>(new NetTcpBinding()))
            {
                var endpointAddress = ConfigurationManager.AppSettings["prestoServiceAddress"];    
                return channelFactory.CreateChannel(new EndpointAddress(endpointAddress));
            }
        }
    }
}

This allows my view models to make the call with just one line of code now:

this.Applications = new ObservableCollection<Application>(PrestoWcf.PrestoService.GetAllApplications().ToList());

However, I get an error the the WcfChannelFactory is already disposed. This makes sense because it really is disposed when the view model tries to use it. But, if I removing the using, then I'm not properly disposing of the WcfChannelFactory. Note, the WcfChannelFactory embeds itself in the WcfClientProxy when CreateChannel() is called. This is why/how the view model is trying to use it after it has been disposed.

How do I abstract this code, to keep my view model calls as simple as possible, while properly disposing WcfChannelFactory? I hope I explained this well enough.

Edit - Solved!

Based on steaks answer, this did it:

public static class PrestoWcf
{
    public static T Invoke<T>(Func<IPrestoService, T> func)
    {
        using (var channelFactory = new WcfChannelFactory<IPrestoService>(new NetTcpBinding()))
        {
            var endpointAddress = ConfigurationManager.AppSettings["prestoServiceAddress"];

            IPrestoService prestoService = channelFactory.CreateChannel(new EndpointAddress(endpointAddress));
            return func(prestoService);
        }
    }
}

And here is the view model call:

this.Applications = new ObservableCollection<Application>(PrestoWcf.Invoke(service => service.GetAllApplications()).ToList());

回答1:

Something like the following may help

public static void UsePrestoService(Action<IPrestoService> callback)
{
    using (var channelFactory = new WcfChannelFactory<IPrestoService>(new NetTcpBinding()))
    {
        var endpointAddress = ConfigurationManager.AppSettings["prestoServiceAddress"];
        IPrestoService prestoService = channelFactory.CreateChannel(new EndpointAddress(endpointAddress));  
        //Now you have access to the service before the channel factory is disposed.  But you don't have to worry about disposing the channel factory.
        callback(prestoService);
    }
}

UsePrestoService(service => this.Applications = new ObservableCollection<Application>(service.GetAllApplications().ToList()));

Side note:

I haven't used this pattern much with disposables because I haven't found too much of a need for disposables recently. However, in theory I think I like this pattern, taking a callback that executes inside of a using block, when working with disposables for two reasons:

  1. It's simple
  2. It forces consumers of IDisposables to dispose of the instances correctly...Although I agree (I think) with the C#'s team to not raise compiler warnings when IDisposables aren't disposed of in all execution paths, it's still a bit worrisome.


回答2:

Are you sure to use service locator pattern there? Beside it is an anti-pattern, by using Invoke<T> and Func<T, TResult>, I think there will be some confusion in the future use. Furthermore, I don't think that this way will separate the usage of service to another layer.

I think this approach, by returning the result, is having more SOC than using Func<T, TResult>.

public static class PrestoWcf
{
    public static IEnumerable<Application> PrestoService
    {
        get
        {
            IEnumerable<Application> appList;
            using (var channelFactory = new WcfChannelFactory<IPrestoService>(new NetTcpBinding()))
            {
                var endpointAddress = ConfigurationManager.AppSettings["prestoServiceAddress"];    
                appList = prestoService.GetAllApplications().ToArray(); //you can ignore the .ToArray, I just not sure whether the enumerable still keep the reference to service
            }
            return appList;
        }
    }
}

Cleaner, but still i don't suggest to use static method.



标签: c# dispose