Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add overloads for custom disposable object creation method. #159

Closed
wants to merge 1 commit into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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