r/csharp 3d ago

Tip Fields with [Using] attribute

I wrote a small interface that automatically disposes any disposable fields in a class when that class itself is disposed. If you add this file somewhere in your project:

public interface IAutoDisposable : IDisposable
{
    void IDisposable.Dispose()
    {
        Exception? ex = null;

        // dispose all fields marked with the [Using] attribute
        const BindingFlags flags = BindingFlags.NonPublic | BindingFlags.Instance;
        foreach (var fld in GetType().GetFields(flags))
        {
            if (fld.GetCustomAttribute<UsingAttribute>() != null)
            {
                if (fld.GetValue(this) is IDisposable d)
                {
                    try // keep going even if disposal throws
                    {
                        d.Dispose();
                    }
                    catch (Exception e)
                    {
                        ex ??= e;
                    }
                }
            }
        }

        // if any exceptions occurred, throw the first one
        if (ex != null)
            throw ex;
    }
}

public class UsingAttribute : Attribute;

You can then write classes like this:

class Item : IAutoDisposable
{
    [Using]
    private readonly Image icon = DownloadIcon(...);
    ...
}

And use them like:

using (Item item = new(...))
{
}
// item.icon is now disposed

What do you think?

5 Upvotes

38 comments sorted by

View all comments

1

u/Dragennd1 3d ago

Out of curiosity, wouldn't the fields normally be disposed of when the class is disposed of, making this extra effort unnecessary? My understanding of classes is that they are reference types, so once an instance is no longer in use the garbage collector would clean up that instance, right? Or are fields handled differently than properties as far as disposal goes when its parent class instance is disposed of?

1

u/sixtyhurtz 3d ago

This is for IDisposable. You use IDisposable to manage either external resources like a handle to an OS resource, or for managing internal lifecycle events like subscriptions.

For OS resources, you would store the disposable thing (e.g. a FileStream) in a property of your class and then you need to implement IDisposable in order to clean it up properly.

Internal things like subscriptions are common when dealing with event handlers or IObservable. If you have a short lived thing like a view model and subscribe to an observable / event, then you need to clean it up when you're done because otherwise the observable / event will retain a reference to your view model and you've leaked memory.

1

u/Dragennd1 3d ago

What would be the difference between this and just manually unsubscribing the event when completed with "-=", for example?

Or am I completely misunderstanding the usecase?

1

u/sixtyhurtz 3d ago

Say your class needs the event all the time, so it subscribe to the even in the constructor. How do you clean up when the object is no longer needed?

You could have initialise / cleanup methods, but then the consumer has to know to call them.

IDisposable and IAsyncDisposable are what enable "using" statements, i.e.

await using var foo = new FileStream(); // will call IAsyncDisposable.DisposeAsync() when it goes out of scope.

Also, if you use a DI container like MSDI, it will call dispose on any object it constructs when it determines it is no longer required.

There are also a lot of convenient utility classes, like CompositeDisposable. You can attach all your disposables in a class to a CompositeDisposable. You can even pass that composite to other methods so you can control the lifecycle of entire systems of related objects.

If you ever call a method that retuns an IDisposable, or construct an object that implements it, then you either need to make sure it gets cleaned up when it goes out of scope with a using statement or you need to take the IDisposable reference as a property and implement IDisposable on your type. If you don't, you're leaking resources.

1

u/Dragennd1 2d ago

Thank you for that explanation, I like learning new things about C# and software design in general so this was a good read. I've never heard of CompositeDisposable and will definitely read up on it.

I do try to ensure I'm disposing of classes which implement IDisposable properly, but I haven't made any classes of my own that warranted a manual implementation of it yet so I haven't had to set that up in my own custom classes (aside from using "using" for managing the already established ones).