Skip to content

Commit

Permalink
Add overloads for custom disposable creation method.
Browse files Browse the repository at this point in the history
  • Loading branch information
AbbyyNikolaiKolmykov committed Mar 6, 2024
1 parent f876cd5 commit b8791c8
Show file tree
Hide file tree
Showing 3 changed files with 175 additions and 7 deletions.
2 changes: 1 addition & 1 deletion CodeJam.Main.Tests/DisposableExtensionsTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,7 @@ public static void DisposeAllMustCollectAllExceptions()

#if NETSTANDARD21_OR_GREATER || NETCOREAPP30_OR_GREATER
[Test]
public static async Task DisposeAsyncMustCallDiposeOnce()
public static async Task DisposeAsyncMustCallDisposeOnce()
{
const int expectedDisposeCount = 1;

Expand Down
83 changes: 80 additions & 3 deletions CodeJam.Main.Tests/DisposableTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -115,7 +115,7 @@ public static void TestParameterizedAnonymousDisposable()
var state = "";
var disposed = false;

using (Disposable.Create(s => {disposed = true; state = s;}, "state"))
using (Disposable.Create(s => { disposed = true; state = s; }, "state"))
{
}

Expand Down Expand Up @@ -180,13 +180,90 @@ public static void InitDisposeTest4()
s =>
{
Assert.That(++i, Is.EqualTo(3));
Assert.That(s, Is.EqualTo("123"));
Assert.That(s, Is.EqualTo("123"));
}))
{
Assert.That(++i, Is.EqualTo(2));
}

Assert.That(++i, Is.EqualTo(4));
}

[Test]
public static void CustomDisposeMustProcessSingleEntity()
{
const int expectedInitializeCount = 1;
const int expectedUseCount = 1;
const int expectedDestroyCount = 1;

int actualInitializeCount = 0;
int actualUseCount = 0;
int actualDisposeCount = 0;

var rawTestObject = new SomeTestableEntity(
() => ++actualInitializeCount,
() => ++actualUseCount,
() => ++actualDisposeCount);

var wrappedTestObject = Disposable.Create(
() => { rawTestObject.Initialize(); return rawTestObject; },
innerTestableEntity => innerTestableEntity.Destroy());

Assert.DoesNotThrow(wrappedTestObject.Entity.Use);
Assert.DoesNotThrow(wrappedTestObject.Dispose);
Assert.Throws<ObjectDisposedException>(() => wrappedTestObject.Entity.Use());

Assert.AreEqual(expectedInitializeCount, actualInitializeCount);
Assert.AreEqual(expectedUseCount, actualUseCount);
Assert.AreEqual(expectedDestroyCount, actualDisposeCount);
}

[Test]
public static void CustomDisposeMustProcessMultipleEntities()
{
const int expectedEntitiesCount = 10;
const int expectedInitializeCount = 10;
const int expectedDestroyCount = 10;

int actualEntitiesCount = 0;
int actualInitializeCount = 0;
int actualDisposeCount = 0;

var create = () => new SomeTestableEntity(
() => ++actualInitializeCount,
() => { },
() => ++actualDisposeCount);

var wrappedTestObject = Disposable.Create(
() => Enumerable.Range(0, expectedEntitiesCount),
index => { var rawTestObject = create(); rawTestObject.Initialize(); return rawTestObject; },
innerTestableEntity => innerTestableEntity.Destroy());

Assert.DoesNotThrow(() => actualEntitiesCount = wrappedTestObject.Entities.Count());
Assert.DoesNotThrow(wrappedTestObject.Dispose);
Assert.Throws<ObjectDisposedException>(() => actualEntitiesCount = wrappedTestObject.Entities.Count());

Assert.AreEqual(expectedEntitiesCount, actualEntitiesCount);
Assert.AreEqual(expectedInitializeCount, actualInitializeCount);
Assert.AreEqual(expectedDestroyCount, actualDisposeCount);
}

private class SomeTestableEntity
{
private readonly Action _initialize;
private readonly Action _use;
private readonly Action _destroy;

public SomeTestableEntity(Action initialize, Action use, Action destroy)
{
_initialize = initialize;
_use = use;
_destroy = destroy;
}

public void Initialize() => _initialize();
public void Use() => _use();
public void Destroy() => _destroy();
}
}
}
}
97 changes: 94 additions & 3 deletions CodeJam.Main/Disposable.cs
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
using System;
using JetBrains.Annotations;

using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading;

using JetBrains.Annotations;

namespace CodeJam
{
/// <summary>Helper methods for <see cref="IDisposable"/></summary>
Expand Down Expand Up @@ -102,6 +103,63 @@ private bool OnException(Action<T?> disposeAction)
return false;
}
}

/// <summary>
/// Disposable wrapper for single object.
/// </summary>
/// <typeparam name="T">Type of wrapped object that needed to be deinitialized.</typeparam>
public class CustomDisposable<T> : IDisposable
{
private readonly Action<T> _destroyer;

private bool _wasDisposed = false;

private readonly T _entity;

public T Entity => (_wasDisposed ? throw new ObjectDisposedException(nameof(_entity)) : _entity);

public CustomDisposable(T entity, Action<T> destroyer)
{
_entity = entity;
_destroyer = destroyer;
}

public void Dispose()
{
if (_wasDisposed) return; else _wasDisposed = true;
_destroyer(_entity);
}
}

/// <summary>
/// Disposable wrapper for multiple objects.
/// </summary>
/// <typeparam name="TE">Type of wrapped objects collection, in which each element must be deinitialized.</typeparam>
/// <typeparam name="T">Type of element of wrapped objects collection.</typeparam>
public class CustomDisposable<TE, T> : IDisposable
where TE : IEnumerable<T>
{

private readonly Action<T> _destroyer;

private bool _wasDisposed = false;

private readonly TE _entities;

public TE Entities => (_wasDisposed ? throw new ObjectDisposedException(nameof(_entities)) : _entities);

public CustomDisposable(TE entities, Action<T> destroyer)
{
_entities = entities;
_destroyer = destroyer;
}

public void Dispose()
{
if (_wasDisposed) return; else _wasDisposed = true;
_entities.Select(e => (IDisposable)new CustomDisposable<T>(e, _destroyer)).DisposeAll();
}
}
#endregion

/// <summary><see cref="IDisposable"/> instance without any code in <see cref="IDisposable.Dispose"/>.</summary>
Expand Down Expand Up @@ -130,6 +188,39 @@ private bool OnException(Action<T?> disposeAction)
public static IDisposable Create<T>(Action<T?> disposeAction, T? state) =>
new AnonymousDisposable<T>(disposeAction, state);

/// <summary>
/// Creates disposable wrapper for single object.
/// </summary>
/// <typeparam name="T">Type of wrapped object that needed to be deinitialized.</typeparam>
/// <param name="creator">Used at place immediately to initialize internal entity inside returning disposable wrapper.</param>
/// <param name="destroyer">Used during internal entity deinitialization as an action of the dispose method.</param>
/// <returns>Created disposable wrapper of single object.</returns>
public static CustomDisposable<T> Create<T>(Func<T> creator, Action<T> destroyer) =>
new(creator(), destroyer);

/// <summary>
/// Creates disposable wrapper for multiple objects.
/// </summary>
/// <typeparam name="TE">Type of wrapped objects collection, in which each element must be deinitialized.</typeparam>
/// <typeparam name="T">Type of element of wrapped objects collection.</typeparam>
/// <param name="creator">Used at place immediately to initialize internal entities inside returning disposable wrapper.</param>
/// <param name="destroyer">Used during each internal entity deinitialization as an action of the dispose method.</param>
/// <returns>Created disposable wrapper of multiple objects.</returns>
public static CustomDisposable<TE, T> Create<TE, T>(Func<TE> creator, Action<T> destroyer) where TE : IEnumerable<T> =>
new(creator(), destroyer);

/// <summary>
/// Creates disposable wrapper for multiple objects.
/// </summary>
/// <typeparam name="TTe">Type of wrapped objects collection, in which each element must be deinitialized.</typeparam>
/// <typeparam name="T">Type of element of wrapped objects collection.</typeparam>
/// <param name="counter">Used at place immediately to get source collection for internal entities initialize enumeration.</param>
/// <param name="creator">Used at place immediately to initialize each internal entity inside returning disposable wrapper.</param>
/// <param name="destroyer">Used during each internal entity deinitialization as an action of the dispose method.</param>
/// <returns>Created disposable wrapper of multiple objects.</returns>
public static CustomDisposable<IEnumerable<T>, T> Create<TTe, T>(Func<IEnumerable<TTe>> counter, Func<TTe, T> creator, Action<T> destroyer) =>
new(counter().Select(creator).ToArray(), destroyer);

/// <summary>Combine multiple <see cref="IDisposable"/> instances into single one.</summary>
/// <param name="disposables">The disposables.</param>
/// <returns>Instance of <see cref="IDisposable"/> that will dispose the specified disposables.</returns>
Expand Down

0 comments on commit b8791c8

Please sign in to comment.