Skip to content

Latest commit

 

History

History
313 lines (263 loc) · 8.99 KB

File metadata and controls

313 lines (263 loc) · 8.99 KB

Tracking disposable instances using pre-built classes

If you want ready-made classes for tracking disposable objects in your libraries but don't want to create your own, you can add this package to your projects:

NuGet

It contains attributes like Inject and Inject<T> that work for constructors and their arguments, methods and their arguments, properties and fields. They allow you to setup all injection parameters.

using Shouldly;
using Pure.DI.Abstractions;
using Pure.DI;

var composition = new Composition();
var dataService1 = composition.DataService;
var dataService2 = composition.DataService;

// The dedicated connection should be unique for each root
dataService1.Connection.ShouldNotBe(dataService2.Connection);

// The shared connection should be the same instance
dataService1.SharedConnection.ShouldBe(dataService2.SharedConnection);

dataService2.Dispose();

// Checks that the disposable instances
// associated with dataService2 have been disposed of
dataService2.Connection.IsDisposed.ShouldBeTrue();

// But the singleton is still not disposed of
// because it is shared and tracked by the composition
dataService2.SharedConnection.IsDisposed.ShouldBeFalse();

// Checks that the disposable instances
// associated with dataService1 have not been disposed of
dataService1.Connection.IsDisposed.ShouldBeFalse();
dataService1.SharedConnection.IsDisposed.ShouldBeFalse();

dataService1.Dispose();

// Checks that the disposable instances
// associated with dataService1 have been disposed of
dataService1.Connection.IsDisposed.ShouldBeTrue();

// But the singleton is still not disposed of
dataService1.SharedConnection.IsDisposed.ShouldBeFalse();

composition.Dispose();

// The shared singleton is disposed only when the composition is disposed
dataService1.SharedConnection.IsDisposed.ShouldBeTrue();

interface IDbConnection
{
    bool IsDisposed { get; }
}

class DbConnection : IDbConnection, IDisposable
{
    public bool IsDisposed { get; private set; }

    public void Dispose() => IsDisposed = true;
}

interface IDataService
{
    public IDbConnection Connection { get; }

    public IDbConnection SharedConnection { get; }
}

class DataService(
    Func<Own<IDbConnection>> connectionFactory,
    [Tag("shared")] Func<Own<IDbConnection>> sharedConnectionFactory)
    : IDataService, IDisposable
{
    // Own<T> is a wrapper from Pure.DI.Abstractions that owns the value.
    // It ensures that the value is disposed when Own<T> is disposed,
    // but only if the value is not a singleton or externally owned.
    private readonly Own<IDbConnection> _connection = connectionFactory();
    private readonly Own<IDbConnection> _sharedConnection = sharedConnectionFactory();

    public IDbConnection Connection => _connection.Value;

    public IDbConnection SharedConnection => _sharedConnection.Value;

    public void Dispose()
    {
        // Disposes the dedicated connection
        _connection.Dispose();

        // Notifies that we are done with the shared connection.
        // However, since it's a singleton, the underlying instance won't be disposed here.
        _sharedConnection.Dispose();
    }
}

partial class Composition
{
    static void Setup() =>

        DI.Setup()
            .Bind().To<DbConnection>()
            .Bind("shared").As(Lifetime.Singleton).To<DbConnection>()
            .Bind().To<DataService>()

            // Composition root
            .Root<DataService>("DataService");
}
Running this code sample locally
dotnet --list-sdk
  • Create a net10.0 (or later) console application
dotnet new console -n Sample
dotnet add package Pure.DI
dotnet add package Shouldly
dotnet add package Pure.DI.Abstractions
  • Copy the example code into the Program.cs file

You are ready to run the example 🚀

dotnet run

This package should also be included in a project:

NuGet

The following partial class will be generated:

partial class Composition: IDisposable
{
#if NET9_0_OR_GREATER
  private readonly Lock _lock = new Lock();
#else
  private readonly Object _lock = new Object();
#endif
  private object[] _disposables = new object[1];
  private int _disposeIndex;

  private DbConnection? _singletonDbConnection63;

  public DataService DataService
  {
    [MethodImpl(MethodImplOptions.AggressiveInlining)]
    get
    {
      var perBlockOwn174 = new Abstractions.Own();
      Func<Abstractions.Own<IDbConnection>> perBlockFunc172 = new Func<Abstractions.Own<IDbConnection>>(
      [MethodImpl(MethodImplOptions.AggressiveInlining)]
      () =>
      {
        Abstractions.Own<IDbConnection> perBlockOwn175;
        // Creates the owner of an instance
        Abstractions.Own localOwn = perBlockOwn174;
        var transientDbConnection176 = new DbConnection();
        lock (_lock)
        {
          perBlockOwn174.Add(transientDbConnection176);
        }

        IDbConnection localValue12 = transientDbConnection176;
        perBlockOwn175 = new Abstractions.Own<IDbConnection>(localValue12, localOwn);
        lock (_lock)
        {
          perBlockOwn174.Add(perBlockOwn175);
        }

        return perBlockOwn175;
      });
      var perBlockOwn177 = new Abstractions.Own();
      Func<Abstractions.Own<IDbConnection>> perBlockFunc173 = new Func<Abstractions.Own<IDbConnection>>(
      [MethodImpl(MethodImplOptions.AggressiveInlining)]
      () =>
      {
        Abstractions.Own<IDbConnection> perBlockOwn178;
        // Creates the owner of an instance
        Abstractions.Own localOwn1 = perBlockOwn177;
        if (_singletonDbConnection63 is null)
          lock (_lock)
            if (_singletonDbConnection63 is null)
            {
              _singletonDbConnection63 = new DbConnection();
              _disposables[_disposeIndex++] = _singletonDbConnection63;
            }

        IDbConnection localValue14 = _singletonDbConnection63;
        perBlockOwn178 = new Abstractions.Own<IDbConnection>(localValue14, localOwn1);
        lock (_lock)
        {
          perBlockOwn177.Add(perBlockOwn178);
        }

        return perBlockOwn178;
      });
      return new DataService(perBlockFunc172, perBlockFunc173);
    }
  }

  public void Dispose()
  {
    int disposeIndex;
    object[] disposables;
    lock (_lock)
    {
      disposeIndex = _disposeIndex;
      _disposeIndex = 0;
      disposables = _disposables;
      _disposables = new object[1];
      _singletonDbConnection63 = null;
    }

    while (disposeIndex-- > 0)
    {
      switch (disposables[disposeIndex])
      {
        case IDisposable disposableInstance:
          try
          {
            disposableInstance.Dispose();
          }
          catch (Exception exception)
          {
            OnDisposeException(disposableInstance, exception);
          }
          break;
      }
    }
  }

  partial void OnDisposeException<T>(T disposableInstance, Exception exception) where T : IDisposable;
}

Class diagram:

---
 config:
  maxTextSize: 2147483647
  maxEdges: 2147483647
  class:
   hideEmptyMembersBox: true
---
classDiagram
	Composition --|> IDisposable
	DbConnection --|> IDbConnection
	DataService --|> IDataService
	Composition ..> DataService : DataService DataService
	DataService o-- "PerBlock" FuncᐸOwnᐸIDbConnectionᐳᐳ : FuncᐸOwnᐸIDbConnectionᐳᐳ
	DataService o-- "PerBlock" FuncᐸOwnᐸIDbConnectionᐳᐳ : "shared"  FuncᐸOwnᐸIDbConnectionᐳᐳ
	FuncᐸOwnᐸIDbConnectionᐳᐳ o-- "PerBlock" OwnᐸIDbConnectionᐳ : OwnᐸIDbConnectionᐳ
	FuncᐸOwnᐸIDbConnectionᐳᐳ o-- "PerBlock" OwnᐸIDbConnectionᐳ : "shared"  OwnᐸIDbConnectionᐳ
	OwnᐸIDbConnectionᐳ *--  DbConnection : IDbConnection
	OwnᐸIDbConnectionᐳ o-- "PerBlock" Own : Own
	OwnᐸIDbConnectionᐳ o-- "Singleton" DbConnection : "shared"  IDbConnection
	OwnᐸIDbConnectionᐳ o-- "PerBlock" Own : Own
	namespace Pure.DI.Abstractions {
		class Own {
				<<class>>
		}
		class OwnᐸIDbConnectionᐳ {
				<<struct>>
		}
	}
	namespace Pure.DI.UsageTests.Advanced.TrackingDisposableWithAbstractionsScenario {
		class Composition {
		<<partial>>
		+DataService DataService
		}
		class DataService {
				<<class>>
			+DataService(FuncᐸOwnᐸIDbConnectionᐳᐳ connectionFactory, FuncᐸOwnᐸIDbConnectionᐳᐳ sharedConnectionFactory)
		}
		class DbConnection {
				<<class>>
			+DbConnection()
		}
		class IDataService {
			<<interface>>
		}
		class IDbConnection {
			<<interface>>
		}
	}
	namespace System {
		class FuncᐸOwnᐸIDbConnectionᐳᐳ {
				<<delegate>>
		}
		class IDisposable {
			<<abstract>>
		}
	}
Loading