Skip to content

Commit 3d0cefe

Browse files
authored
Merge pull request #8 from Z3roTech/feature/core-features-of-library
add core features of library
2 parents 4a21557 + 105302c commit 3d0cefe

17 files changed

+772
-75
lines changed

ZeroCode.Core/AssemblyInfo.cs

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
using System.Runtime.CompilerServices;
2+
using System.Runtime.InteropServices;
3+
4+
// In SDK-style projects such as this one, several assembly attributes that were historically
5+
// defined in this file are now automatically added during build and populated with
6+
// values defined in project properties. For details of which attributes are included
7+
// and how to customise this process see: https://aka.ms/assembly-info-properties
8+
9+
// Setting ComVisible to false makes the types in this assembly not visible to COM
10+
// components. If you need to access a type in this assembly from COM, set the ComVisible
11+
// attribute to true on that type.
12+
13+
[assembly: ComVisible(false)]
14+
15+
// The following GUID is for the ID of the typelib if this project is exposed to COM.
16+
17+
[assembly: Guid("6c2d97cd-45cc-4a52-8210-6fef9c11d080")]
18+
[assembly: InternalsVisibleTo("ZeroCode.Tests")]
Lines changed: 184 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,184 @@
1+
using System;
2+
using System.Collections.Concurrent;
3+
using System.Threading;
4+
using System.Threading.Tasks;
5+
6+
namespace ZeroCode
7+
{
8+
/// <summary>
9+
/// Helper class for invoking methods in concurrent mode. It may help to queue requests to database on application
10+
/// layer instead of database layer.
11+
/// </summary>
12+
public class ConcurrentExecutor
13+
{
14+
/// <summary>
15+
/// Synchronization primitive that will be control concurrent methods invoke
16+
/// </summary>
17+
private readonly SemaphoreSlim _semaphore;
18+
19+
/// <summary>
20+
/// Count of maximum tasks of invoking methods that can be executed in meantime
21+
/// </summary>
22+
private int _maxParallelTasks;
23+
24+
public ConcurrentExecutor(int maxParallelTasks)
25+
{
26+
if (maxParallelTasks <= 0)
27+
throw new ArgumentOutOfRangeException(nameof(maxParallelTasks),
28+
"Value of maximum parallel executing tasks must be more then 0");
29+
30+
_maxParallelTasks = maxParallelTasks;
31+
_semaphore = new SemaphoreSlim(_maxParallelTasks);
32+
}
33+
34+
/// <summary>
35+
/// Change count of maximum methods that will be invoking in meantime. This method can be executing too long when
36+
/// lowering count of maximum parallel tasks.
37+
/// </summary>
38+
/// <param name="maxParallelTasks"></param>
39+
/// <returns></returns>
40+
/// <exception cref="ArgumentOutOfRangeException"></exception>
41+
public ConcurrentExecutor ChangeMaxParallelTaskCount(int maxParallelTasks)
42+
{
43+
if (maxParallelTasks <= 0)
44+
throw new ArgumentOutOfRangeException(nameof(maxParallelTasks),
45+
"Value of maximum parallel executing tasks must be more then 0");
46+
47+
var oldValue = Interlocked.Exchange(ref _maxParallelTasks, maxParallelTasks);
48+
var diff = maxParallelTasks - oldValue;
49+
50+
// If value of max parallel now more than previous, we must release new slots in semaphore
51+
if (diff > 0) _semaphore.Release(diff);
52+
53+
// If value of max parallel tasks now less than previous, we must use semaphore slots
54+
if (diff < 0)
55+
for (var i = 0; i < Math.Abs(diff); i++)
56+
_semaphore.Wait();
57+
58+
return this;
59+
}
60+
61+
/// <summary>
62+
/// Execute in concurrent mode <paramref name="wrapped" />
63+
/// </summary>
64+
/// <typeparam name="TResult">Any result type</typeparam>
65+
/// <param name="wrapped">Method that must be invoked in concurrent mode</param>
66+
/// <param name="timeout">Timeout of waiting invoking in concurrent queue</param>
67+
/// <param name="token"></param>
68+
/// <returns></returns>
69+
/// <exception cref="ArgumentNullException"></exception>
70+
/// <exception cref="TimeoutException">If execution was timed out</exception>
71+
public TResult Execute<TResult>(Func<TResult> wrapped, TimeSpan timeout = default,
72+
CancellationToken token = default)
73+
{
74+
if (wrapped == null) throw new ArgumentNullException(nameof(wrapped));
75+
76+
try
77+
{
78+
if (timeout == TimeSpan.Zero)
79+
_semaphore.Wait(token);
80+
else if (!_semaphore.Wait(timeout, token)) throw new TimeoutException();
81+
82+
return wrapped();
83+
}
84+
finally
85+
{
86+
_semaphore.Release();
87+
}
88+
}
89+
90+
/// <inheritdoc cref="Execute{TResult}" />
91+
public void Execute(Action wrapped, TimeSpan timeout = default,
92+
CancellationToken token = default)
93+
{
94+
if (wrapped == null) throw new ArgumentNullException(nameof(wrapped));
95+
96+
try
97+
{
98+
if (timeout == TimeSpan.Zero)
99+
_semaphore.Wait(token);
100+
else if (!_semaphore.Wait(timeout, token)) throw new TimeoutException();
101+
102+
wrapped();
103+
}
104+
finally
105+
{
106+
_semaphore.Release();
107+
}
108+
}
109+
110+
/// <inheritdoc cref="Execute{TResult}" />
111+
public async Task<TResult> ExecuteAsync<TResult>(Func<Task<TResult>> wrapped, TimeSpan timeout = default,
112+
CancellationToken token = default)
113+
{
114+
if (wrapped == null) throw new ArgumentNullException(nameof(wrapped));
115+
116+
try
117+
{
118+
if (timeout == TimeSpan.Zero)
119+
await _semaphore.WaitAsync(token);
120+
else if (!await _semaphore.WaitAsync(timeout, token)) throw new TimeoutException();
121+
122+
return await wrapped();
123+
}
124+
finally
125+
{
126+
_semaphore.Release();
127+
}
128+
}
129+
130+
/// <inheritdoc cref="Execute{TResult}" />
131+
public async Task ExecuteAsync(Func<Task> wrapped, TimeSpan timeout = default,
132+
CancellationToken token = default)
133+
{
134+
if (wrapped == null) throw new ArgumentNullException(nameof(wrapped));
135+
136+
try
137+
{
138+
if (timeout == TimeSpan.Zero)
139+
await _semaphore.WaitAsync(token);
140+
else if (!await _semaphore.WaitAsync(timeout, token)) throw new TimeoutException();
141+
142+
await wrapped();
143+
}
144+
finally
145+
{
146+
_semaphore.Release();
147+
}
148+
}
149+
}
150+
151+
/// <summary>
152+
/// Static cache of <see cref="ConcurrentExecutor" />
153+
/// </summary>
154+
public static class ConcurrentExecutorCache
155+
{
156+
/// <summary>
157+
/// Cache of all created named executors
158+
/// </summary>
159+
private static readonly ConcurrentDictionary<string, ConcurrentExecutor> Executors =
160+
new ConcurrentDictionary<string, ConcurrentExecutor>();
161+
162+
/// <summary>
163+
/// Returns existing or created new named executor
164+
/// </summary>
165+
/// <param name="name"></param>
166+
/// <param name="maxParallelTasks"></param>
167+
/// <returns></returns>
168+
/// <exception cref="ArgumentNullException"></exception>
169+
public static ConcurrentExecutor GetOrCreate(string name, int maxParallelTasks)
170+
{
171+
if (name == null) throw new ArgumentNullException(nameof(name));
172+
173+
return Executors.GetOrAdd(name, new ConcurrentExecutor(maxParallelTasks));
174+
}
175+
176+
/// <summary>
177+
/// Clear cache of named executors
178+
/// </summary>
179+
public static void ClearExecutors()
180+
{
181+
Executors.Clear();
182+
}
183+
}
184+
}

ZeroCode.Core/GenericDisposable.cs

Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
using System;
2+
3+
namespace ZeroCode
4+
{
5+
/// <summary>
6+
/// Class implemented <see cref="IDisposable" />, that can be set to invoke custom action on
7+
/// <see cref="IDisposable.Dispose" />. Useful for making custom unsubscribes.
8+
/// </summary>
9+
public class GenericDisposable : IDisposable
10+
{
11+
/// <summary>
12+
/// Empty instance of generic disposable
13+
/// </summary>
14+
public static readonly GenericDisposable Empty = new GenericDisposable();
15+
16+
/// <summary>
17+
/// Action that must be invoked on dispose of this object. Provide state as argument.
18+
/// </summary>
19+
private readonly Action<object?>? _actionOnDispose;
20+
21+
/// <summary>
22+
/// Action that must be invoked on dispose of this object.
23+
/// </summary>
24+
private readonly Action? _actionOnDisposeWithoutState;
25+
26+
/// <summary>
27+
/// Custom state value that will be provided into action method on dispose.
28+
/// </summary>
29+
private readonly object? _state;
30+
31+
public GenericDisposable() { }
32+
33+
/// <param name="actionOnDispose">
34+
/// <inheritdoc cref="_actionOnDisposeWithoutState" />
35+
/// </param>
36+
/// <exception cref="ArgumentNullException"></exception>
37+
public GenericDisposable(Action actionOnDispose)
38+
{
39+
_actionOnDisposeWithoutState = actionOnDispose ?? throw new ArgumentNullException(nameof(actionOnDispose));
40+
}
41+
42+
/// <param name="actionOnDispose">
43+
/// <inheritdoc cref="_actionOnDispose" />
44+
/// </param>
45+
/// <param name="state">
46+
/// <inheritdoc cref="_state" />
47+
/// </param>
48+
/// <exception cref="ArgumentNullException"></exception>
49+
public GenericDisposable(Action<object?> actionOnDispose, object? state)
50+
{
51+
_state = state;
52+
_actionOnDispose = actionOnDispose ?? throw new ArgumentNullException(nameof(actionOnDispose));
53+
}
54+
55+
/// <inheritdoc />
56+
public void Dispose()
57+
{
58+
_actionOnDispose?.Invoke(_state);
59+
_actionOnDisposeWithoutState?.Invoke();
60+
}
61+
}
62+
}
Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
using System;
2+
using System.Collections.Generic;
3+
using System.Linq;
4+
5+
namespace ZeroCode
6+
{
7+
/// <summary>
8+
/// Generic implementation of <see cref="IEqualityComparer{T}" /> that can be used for transforming equality comparer
9+
/// function into implementation of <see cref="IEqualityComparer{T}" />
10+
/// </summary>
11+
/// <typeparam name="T"></typeparam>
12+
public class GenericEqualityComparer<T> : IEqualityComparer<T>
13+
{
14+
/// <summary>
15+
/// Equality comparer function
16+
/// </summary>
17+
private readonly Func<T, T, bool> _comparer;
18+
19+
/// <summary>
20+
/// Functions that returns all candidates for calculating object hash code. Its important because LINQ uses hash codes
21+
/// often for equality compare.
22+
/// </summary>
23+
private readonly Func<T, object?[]> _hashCodeCandidatesFactory;
24+
25+
/// <param name="comparer">
26+
/// <inheritdoc cref="_comparer" />
27+
/// </param>
28+
/// <param name="hashCodeCandidatesFactory">
29+
/// <inheritdoc cref="_hashCodeCandidatesFactory" />
30+
/// </param>
31+
/// <exception cref="ArgumentNullException"></exception>
32+
public GenericEqualityComparer(Func<T, T, bool> comparer, Func<T, object?[]> hashCodeCandidatesFactory)
33+
{
34+
_comparer = comparer ?? throw new ArgumentNullException(nameof(comparer));
35+
_hashCodeCandidatesFactory = hashCodeCandidatesFactory ??
36+
throw new ArgumentNullException(nameof(hashCodeCandidatesFactory));
37+
}
38+
39+
/// <inheritdoc />
40+
public bool Equals(T x, T y)
41+
{
42+
return (x, y) switch
43+
{
44+
(null, null) => true,
45+
(null, _) => false,
46+
(_, null) => false,
47+
(_, _) when ReferenceEquals(x, y) => true,
48+
_ => _comparer(x, y)
49+
};
50+
}
51+
52+
/// <inheritdoc />
53+
public int GetHashCode(T obj)
54+
{
55+
unchecked
56+
{
57+
return _hashCodeCandidatesFactory(obj)
58+
.Aggregate(0, (current, candidate) =>
59+
(current * 397) ^ (candidate != null ? candidate.GetHashCode() : 0)
60+
);
61+
}
62+
}
63+
}
64+
}
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
using System;
2+
using System.Threading;
3+
4+
namespace ZeroCode
5+
{
6+
/// <summary>
7+
/// Helper that can be used to prevent repeated invoke <see cref="IDisposable.Dispose" />
8+
/// </summary>
9+
public class OneTimeDisposingHelper
10+
{
11+
private int _disposingFlag;
12+
13+
/// <summary>
14+
/// Dependent object is already tried to dispose
15+
/// </summary>
16+
public bool IsDisposed => _disposingFlag != 0;
17+
18+
/// <summary>
19+
/// Check this trying to dispose is first time or repeat
20+
/// </summary>
21+
/// <returns></returns>
22+
public bool TryDispose()
23+
{
24+
return Interlocked.CompareExchange(ref _disposingFlag, 1, 0) == 0;
25+
}
26+
}
27+
}

0 commit comments

Comments
 (0)