Search code examples
c#unmanagedidisposableusing-statement

Passing constructor delegate or object for unmanaged resources


In my (simplified) problem I have a method "Reading" that can use many different implementation of some IDisposableThing. I am passing delegates to the constructor right now so I can use the using statement.

Is this approach of passing a delegate of the constructor of my object appropriate? My problem is that things like List<Func<IDisposable>> etc start looking bit scary (because delegates look like crap in c#) and passing in a object seems more usual and a clearer statement of intent.

Is there a better/different way of managing this situation without delegates?

    public void Main()
    {
        Reading(() => new DisposableThingImplementation());
        Reading(() => new AnotherDisposableThingImplementation());
    }

    public void Reading(Func<IDisposableThing> constructor)
    {
        using (IDisposableThing streamReader = constructor())
        {
            //do things
        }
    }

Solution

  • As I said in the comment, it's difficult to say what's best for your situation, so instead I'll just list your options so you can make an informed decision:

    Continue doing what you're doing

    Having to use around objects with an unpleasantly complicated-looking type is maybe not ideal visually, but in your situation it may well be perfectly appropriate

    Use a custom delegate type

    You can define a delegate like:

    public delegate IDisposableThing DisposableThingConstructor();
    

    Then anywhere you would write Func<IDisposableThing>, you can just write DisposableThingConstructor instead. For a commonly used delegate type, this may improve code readability, though this too is a matter of taste.

    Move the using statements out of Reading

    This really depends on whether it's sensible for the lifecycle management of these objects to be a responsibility of the Reading method or not. Given what we have of your code at the moment, we can't really judge this for you. An implementation with the lifecycle management moved out would look like:

    public void Main()
    {
        using(var disposableThing = new DisposableThingImplementation())
            Reading(disposableThing);
    }
    
    public void Reading(IDisposableThing disposableThing)
    {
        //do things
    }
    

    Use a factory pattern

    In this option, you create a class which returns new IDisposableThing implementations. Lots of information can be found on the factory pattern which you may well already know, so I won't repeat it all here. This option may well be overkill for your purposes here, adding a lot of pointless complexity, but depending on how those DisposableThings are constructed, it may have additional benefits which make it worthwhile.

    Use a generic argument

    This option will only work if all of your IDisposableThing implementations have a parameterless constructor. I'm guessing that's not the case, but in case it is, it's a relatively straightforward approach:

    public void Reading<T>() where T : IDisposableThing, new()
    {
        using(var disposableThing = new T())
        {
            //do things
        }
    }
    

    Use an Inversion of Control container

    This is another option which would certainly be overkill if used for this purpose alone. I include it mostly for completeness. Inversion of control containers like Ninject will give you easy ways to manage the lifecycles of objects passed into others.

    I very much doubt this would be an appropriate solution in your case, especially since the disposable objects are not being used in another class's constructor. If you later run into a situation where you're trying to manage object lifecycle in a larger, complex object graph, this option might be worth revisiting.

    Construct the objects outside of the using statement

    This is specifically described as "not a best practice" in the MSDN documentation, but it is an option. You can do:

    public void Main()
    {
        Reading(new DisposableThingImplementation());
    }
    
    public void Reading(IDisposableThing disposableThing)
    {
        using (disposableThing)
        {
            //do things
        }
    }
    

    At the end of the using statement, the Dispose method will be called, but the object will not be garbage collected because it is still in scope. Trying to use the object after that would be very likely to cause problems because it is not fully initialized. So again, while this is an option, it's unlikely to be a good one.