July 23rd, 2023

Dispose is not just for disposing

If you have worked in .NET or any other language that features the dispose pattern, you will probably have written your fair share of using statements (or the equivalent in other languages). The dispose pattern is pretty straightforward. You have some resource that you want to access and then get rid of it once you are done. In .NET this is especially relevant for unmanaged resources. Unlike managed resources, which the garbage collector cleans up after you're done with them, unmanaged resources need to be cleaned up manually. A common example of this is in file IO. You want to access some file but have to make sure that the file handle is released so that other processes can access it. You wrap your code that interacts with the file inside a using block, and you can feel good about yourself because now everything is clean and tidy. But what if I told you that you can leverage the built-in support for this dispose pattern, the using statement, to write scoped code? If you are not aware what the using statement does - it is semantic sugar for try-finally, where the part inside the using statement is put in the try and the Dispose method is called for the object being used inside the finally. So essentially, it's a delayed call of an arbitrary method. With this knowledge, we can create scopes - parts of program control flow in which state is processed differently than in the rest.

Defining the class

Let us define a scope. When the scope is created, we can invoke an initial action. When the scope ends, we can invoke a wrap-up action. The code should be self-explanatory. Just like in my previous article, I used this site to convert the C# code to HTML. The barebones class:

                        public class Scope : IDisposable
                        {
                            protected Action _after;

                            public Scope(Action before, Action after)
                            {
                                _after = after;
                                before?.Invoke();
                            }

                            public void Dispose()
                            {
                                _after?.Invoke();
                            }
                        }
                    

Depending on how you use these scopes, you may want to wrap the calling of the initial action inside a try-catch where you call Dispose (which calls the wrap-up action) if an exception was caught. In that case, the constructor could look like this:

                            public Scope(Action before, Action after)
                            {
                                _after = after;
                                try
                                {
                                    before();
                                }
                                catch
                                {
                                    this.Dispose();
                                    throw;
                                }
                            }
                    

There is a lot more we can add to this. We can support multiple before and after actions, maybe with a fancy params signature to support arbitrary lengths (just need to wrap the before and after inside a new type). Better yet, we can change this whole thing to be an abstract class with abstract void methods Before and After, which would make the inheritor code more ergonomic (passing actions to the base is so barbaric). But instead of doing that, we can have more fun by moving on to the use cases.

Change Tracking

If you have objects with mutable state, you might be using a system that tracks changes and persists only those changes to your data store. Entity Framework is an example of a system that works like this: its entities are attached to an EntityEntry which keeps track of all current and modified values. When you are working with objects like these, there may be moments where you need to set values but not have the entity flagged as being modified. Depending on your implementation of change tracking, a scope can be an elegant solution here.

                        public class NoChangeTrackingScope<T> : IDisposable
                            where T : ITrackChanges
                        {
                            public NoChangeTrackingScope(T entity)
                            {
                                entity.TrackChanges = false;
                            }

                            public void Dispose()
                            {
                                entity.TrackChanges = true;
                            }
                        }
                    

Once again, the actual implementation will depend on how your system handles change tracking. In the case of Entity Framework, you may need to call AcceptChanges first in the constructor. In a codebase I worked on, we used a system like this when setting primary keys. We had two layers of entities, one leveraging Entity Framework to interact with the database, the other interacting with this layer to present the entities to the API. Due to there being an inbetween layer, it made it possible to switch the backing technology from one technology to another with very little additional work, like from ADO.NET to Entity Framework[1]Entity Framework uses ADO.NET under the hood so is this technically a migration from ADO.NET to ADO.NET?. Setting the primary keys normally would flag the entity as being changed. A perfect use case for this scope.

                        using (new NoChangeTrackingScope<Foo>(this))
                        {
                            this.Key = key;
                        }
                    

Scissoring in MonoGame

With that half-theoretical example out of the way, let's go to a more practical one. The caveat - this relies on and augments the MonoGame infrastructure. MonoGame, if you are unfamiliar with it, is a framework for writing games or any applications leveraging a graphical interface in C#. In MonoGame, graphics are sent to the client screen through a buffer. While in the buffer, you can specify a region of the screen (defined by a rectangle) outside of which all graphics are cut. This is called the scissor rectangle. It is useful when painting large scenes, where for reasons of efficiency and performance you may not want to render something the user will not see anyway, because something else may later be drawn over it. For this code I am using as base the Scope class exactly as it was defined earlier in this article.

                        public class ScissorScope : Scope
                        {
                            public ScissorScope(GraphicsDevice device, Rectangle rectangle)
                                : base(ScissorScope.CreateAction(device, rectangle),
                                       ScissorScope.CreateAction(device, device.ScissorRectangle))
                            {
                            }
                    
                            protected static Action CreateAction(GraphicsDevice device, Rectangle rectangle) =>
                                () => device.ScissorRectangle = rectangle;
                        }
                    

Note the important detail - when creating the after action that is passed to the base, the scissor rectangle that is in use before the scope starts is passed in (through device). The action holds this rectangle and can then restore it after the scope ends. If there were no base to call here, then we could simply store the rectangle in the constructor before calling the before action. We can now use this scope while inside the MonoGame Draw method. See this example from one of my personal projects (with changes for brevity):

                        protected override void Draw(GameTime gameTime)
                        {
                            this.GraphicsDevice.Clear(Color.Black);
                
                            using (new ScissorScope(this.GraphicsDevice, this.ScissorContainer))
                            {
                                this.SpriteBatch.Begin(SpriteSortMode.FrontToBack, BlendState.AlphaBlend,
                                    SamplerState.PointWrap, rasterizerState: this.ScissorRasterizer);
                                this.OnDraw?.Invoke<BackgroundDraw>(this.SpriteBatch);
                                this.SpriteBatch.End();
                            }
                
                            this.SpriteBatch.Begin(SpriteSortMode.Deferred, BlendState.AlphaBlend, SamplerState.PointClamp);
                            this.OnDraw?.Invoke<ControlDraw>(this.SpriteBatch);
                            this.SpriteBatch.End();
                        }
                    

With all the boilerplate code moved to the ScissorScope, this method can focus on just control flow.

Custom Dependency Injection

The scope pattern I have been describing in this article is powerful yet simple. I have one final example from my own projects where I use it, but it sits deep inside a complex piece of dependency injection infrastructure. I want to show this one to demonstrate that in the midst of complexity it is all the more important to incorporate the simplicity of patterns like this one. I am using the Into extension method I shared in an earlier article in the below example. Note also that I am passing to the base Scope constructor a ScopedOperation which is nothing more than a wrapper around before and after actions.

                        public class DependencyScope : Scope
                        {
                            public DependencyScope(DependencyHandler dependency)
                                : base(new List<Type>().Into(typeList => new ScopedOperation(
                                    DependencyScope.CreatePreOperation(dependency, typeList),
                                    DependencyScope.CreatePostOperation(dependency, typeList))))
                            {
                            }
                    
                            protected static Action CreatePreOperation(DependencyHandler dependency, List<Type> typeList) =>
                                () => dependency.OnRegister += type => typeList.Add(type);
                    
                            protected static Action CreatePostOperation(DependencyHandler dependency, List<Type> typeList) =>
                                () => typeList.Each(type => dependency.Unregister(type));
                        }
                    

Using Into here lets me preserve the list as I pass it into two different methods while still inside a call to base. As for what this scope does, let me explain the dependency injection infrastructure I am using here. The DependencyHandler manages the registration of various dependencies that are bound to a node in the dependency tree. There may be multiple nodes but there is one root. The dependencies are registered top-down, and each dependency is registered by type. This scope comes into play because some dependency nodes may register the same type as others but without sharing actual instances. This means that the same type is registered multiple times, but since they are registered within a scope, it will not affect the registration of other nodes. Here is an example of a non-root node using this scope to register dependencies. Other nodes will get different dependency instances if they register the same types as those within this scope.

                        public void Register(DependencyHandler dependency)
                        {
                            this.Configuration = dependency.Register<ConfigurationHelper>();
                            using (new DependencyScope(dependency))
                            {
                                this.Camera = dependency.Register<CameraHelper>();
                                this.Tilemap = dependency.Register<TilemapHelper>();
                            }
                        }
                    

In this example the ConfigurationHelper type is registered outside of the scope, which means this dependency is shared with the rest of the dependency tree. The other two types are registered inside the scope, which means the instances of those dependencies are unique to this node. What I hoped to show in this and all the earlier examples is that the code where a scope is being used ends up simple and clean. Move the boilerplate out of the way and not only will your writing be smooth, it also becomes easier to read years later. Years later being when you look back at your old code and wonder why the heck did you write an entire dependency injection system?