Skip to content

Commit b18ecc3

Browse files
authored
Merge pull request #48 from I-RzR-I/feature/LazyAndImprovements
Feature/lazy and improvements
2 parents 4e31156 + 386ac50 commit b18ecc3

31 files changed

Lines changed: 3251 additions & 74 deletions

README.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,9 +5,9 @@
55

66
This library/ repository was created as a way to simplify the development process. Here were written the usually used methods (extension methods) for some data types like `int, string, DateTime, Enum, bool, byte, Guid`, also there was added extensions for `List, Dictionary, DynamicList` and other collections(`ICollection, IEnumerable, IList, HashSet, IQueryable`).
77

8-
In the repository was added an extension for `cryptography`, encrypting and decrypting string by key with `RSA`, `AES`, `TEA``
8+
In the repository was added an extension for `cryptography`, encrypting and decrypting string by key with `RSA`, `AES`, `TEA`.
99

10-
In case you need some get some documentation/comments from `Assembly`, here too are some extensions.
10+
In case you need some get some documentation/comments from `Assembly`, here too are some extensions. Also you can find a async lazy load `AsyncLazy<T>` and `AsyncExpiringLazy<T>`.
1111

1212
Also here you may find extensions for `SystemData (DbDataReader, DataRecord, DataTable)` lib, `FileStream, MemoryStream, Type`, and a lot other of methods and not the last some `Linq` and `Expression` extensions that can help to make code mode clean.
1313

docs/CHANGELOG.md

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,11 @@
1+
### **v4.6.0.8232** [[RzR](mailto:108324929+I-RzR-I@users.noreply.github.com)] 20-03-2026
2+
* [5503224] (RzR) -> Auto commit uncommited files
3+
* [e9aa417] (RzR) -> Add small improvements
4+
* [3982b59] (RzR) -> Add async lazy load `AsyncLazy<T>` and `AsyncExpiringLazy<T>`.
5+
* [ded7012] (RzR) -> Add improvements to the documentation of `TupleResult`
6+
* [879e778] (RzR) -> Add new string extension methods: `IsMissingOrAny`, `IfIsMissingOrAny`.
7+
* [b1a924b] (RzR) -> Add `TupleResult` deconstruct data.
8+
19
### **v4.5.0.7408** [[RzR](mailto:108324929+I-RzR-I@users.noreply.github.com)] 11-02-2026
210
* [a4fd120] (RzR) -> Auto commit uncommited files
311
* [bae196d] (RzR) -> Add enumerable method `SelectWithIsLast` and `WithObservable`

docs/usage.md

Lines changed: 96 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,3 +3,99 @@
33
I think using these methods is no need for any explanations. Here you may find a lot of extensions that help in projects development.
44

55
The source code is open, so you can check what are available and what you can find useful and can use in your project.
6+
7+
---
8+
9+
## Async lazy load `AsyncLazy<T>` and `AsyncExpiringLazy<T>`
10+
11+
## **`AsyncLazy<T>`**
12+
13+
A thread-safe asynchronous lazy initializer. Factory runs exactly once; faults and cancellations auto-reset so the next caller retries.
14+
15+
### Constructor
16+
17+
| Signature | Description | Throws |
18+
|---|---|---|
19+
| `AsyncLazy(Func<CancellationToken, Task<T>> factory)` | Registers the factory delegate. The factory is called on first demand, and again after any fault or cancellation. The `CancellationToken` it receives is cancelled by `Reset()` if called while the factory is running. | `ArgumentNullException``factory` is `null` |
20+
21+
### Properties
22+
23+
| Property | Type | Description |
24+
|---|---|---|
25+
| `IsValueCreated` | `bool` | `true` only when the factory completed successfully and the result has not been cleared by `Reset()`. `false` while in-progress, faulted, cancelled, or after reset. |
26+
| `IsInitializationStarted` | `bool` | `true` once `GetValueAsync()` has been called at least once and the state has not been cleared. Stays `true` while the factory is still running or has faulted, until `Reset()` is called. |
27+
28+
### Methods
29+
30+
| Signature | Returns | Description | Notes |
31+
|---|---|---|---|
32+
| `GetValueAsync(CancellationToken ct = default)` | `Task<T>` | Returns the cached task on subsequent calls. Starts the factory on the first call. All concurrent callers share the same in-progress task. | If the factory faults or is cancelled, state is auto-cleared so the next call retries from scratch. The token only affects the current in-flight factory; other concurrent waiters are not independently cancelled. |
33+
| `TryGetValue(out T value)` | `bool` | Synchronously returns the cached value without triggering initialization. | Returns `false` if not yet computed, still in-progress, faulted, or reset. Zero allocations — safe to call on hot paths. |
34+
| `Reset()` | `void` | Atomically clears state and cancels any in-progress factory invocation. After this call both `IsValueCreated` and `IsInitializationStarted` return `false`. | Safe to call concurrently or multiple times. |
35+
| `GetAwaiter()` | `TaskAwaiter<T>` | Enables `await myLazy` shorthand syntax equivalent to `await myLazy.GetValueAsync()`. ||
36+
37+
### Behavior summary
38+
39+
| Scenario | Outcome |
40+
|---|---|
41+
| First call | Factory invoked; all concurrent callers share the same `Task<T>` |
42+
| Subsequent calls (success) | Cached `Task<T>` returned immediately — factory not called again |
43+
| Factory faults | State auto-cleared; next `GetValueAsync()` call retries |
44+
| Factory cancelled | State auto-cleared; next `GetValueAsync()` call retries |
45+
| `Reset()` while in-progress | Linked `CancellationToken` is cancelled; factory receives cancellation |
46+
| Concurrent `Reset()` + `GetValueAsync()` | Safe — both operate on a single atomically-swapped `LazyState` reference |
47+
48+
---
49+
50+
## `AsyncExpiringLazy<T>`
51+
52+
A thread-safe asynchronous lazy initializer with a configurable TTL. After expiry the next caller transparently triggers a fresh factory invocation.
53+
54+
### Constructor
55+
56+
| Signature | Description | Throws |
57+
|---|---|---|
58+
| `AsyncExpiringLazy(Func<CancellationToken, Task<T>> factory, TimeSpan ttl)` | Registers the factory and TTL window. The factory is called on first access, after expiry, after a fault/cancellation, or after `Reset()`. | `ArgumentNullException``factory` is `null` |
59+
| | | `ArgumentOutOfRangeException``ttl` is negative |
60+
61+
### Properties
62+
63+
| Property | Type | Description |
64+
|---|---|---|
65+
| `IsValueCreated` | `bool` | `true` only when the factory completed successfully **and** the cached result is still within its TTL window. `false` if not computed, faulted, expired, or reset. |
66+
| `IsInitializationStarted` | `bool` | `true` once the first `GetValueAsync()` call has been made and `Reset()` has not cleared the entry. **Important:** remains `true` even after the TTL expires, because the entry object still exists until it is replaced or cleared. |
67+
68+
### Methods
69+
70+
| Signature | Returns | Description | Notes |
71+
|---|---|---|---|
72+
| `GetValueAsync(CancellationToken ct = default)` | `Task<T>` | Returns the cached value if within TTL and healthy. Otherwise starts a fresh factory invocation. Concurrent callers during a single factory run share the same in-progress task (thundering-herd guard). | Faulted or cancelled entries are treated as immediately expired regardless of TTL, allowing instant retry. |
73+
| `TryGetValue(out T value)` | `bool` | Synchronously returns the cached value without triggering a factory invocation or refreshing an expired entry. | Returns `false` if the entry is missing, expired, or faulted. Zero allocations — safe on hot paths. |
74+
| `Reset()` | `void` | Immediately evicts the cached entry regardless of the remaining TTL. After this call both `IsValueCreated` and `IsInitializationStarted` return `false`. | Any in-progress factory run is **not** cancelled — it will complete its `TaskCompletionSource`, but the result is invisible to new callers. Safe to call concurrently or multiple times. |
75+
| `GetAwaiter()` | `TaskAwaiter<T>` | Enables `await myExpiringLazy` shorthand syntax equivalent to `await myExpiringLazy.GetValueAsync()`. ||
76+
77+
### Behavior summary
78+
79+
| Scenario | Outcome |
80+
|---|---|
81+
| First call | Factory invoked; entry stored with expiration = `UtcNow + ttl` |
82+
| Subsequent calls within TTL (healthy) | Cached `Task<T>` returned immediately |
83+
| TTL elapsed | Next `GetValueAsync()` triggers a fresh factory invocation |
84+
| `ttl = TimeSpan.Zero` | Entry expires immediately after creation; factory called on every request |
85+
| Factory faults (async) | Entry treated as expired; next call retries without waiting for TTL |
86+
| Factory throws synchronously | No CAS is performed; factory re-invoked on every request until it succeeds |
87+
| Concurrent callers during refresh | Only the CAS winner invokes the factory; losers await the winner's task |
88+
| `Reset()` while in-progress | Entry cleared; next caller gets a fresh factory run (old run still completes but its result is discarded) |
89+
90+
---
91+
92+
## Side-by-side comparison
93+
94+
| Feature | `AsyncLazy<T>` | `AsyncExpiringLazy<T>` |
95+
|---|---|---|
96+
| **Expiry** | Never — cached forever until `Reset()` | After configured TTL |
97+
| **Reset behaviour** | Cancels the in-progress factory via linked `CancellationToken` | Clears the entry; in-progress factory is **not** cancelled |
98+
| **Fault recovery** | Auto-reset on fault/cancel; next caller retries | Auto-reset on fault/cancel; instant retry regardless of TTL |
99+
| **Constructor params** | `factory` | `factory` + `ttl` |
100+
| **`IsInitializationStarted` after TTL** | N/A | Still `true` until replaced or `Reset()` |
101+
| **Use when** | Value must be computed once and reused indefinitely | Value needs periodic refresh (tokens, configs, rates, etc) |

src/DomainCommonExtensions/Collections/DisposableStackCollection.cs

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,11 @@ public class DisposableStackCollection : IDisposable
3838
/// </summary>
3939
private Stack<IDisposable> _disposable = new Stack<IDisposable>();
4040

41+
/// <summary>
42+
/// Tracks whether this instance has already been disposed.
43+
/// </summary>
44+
private bool _disposed;
45+
4146
/// <summary>
4247
/// Gets the stack length.
4348
/// </summary>
@@ -49,11 +54,18 @@ public class DisposableStackCollection : IDisposable
4954
/// <inheritdoc />
5055
public void Dispose()
5156
{
57+
if (_disposed)
58+
return;
59+
60+
_disposed = true;
61+
5262
if (_disposable.IsNotNull())
5363
{
5464
while (_disposable.Count.IsGreaterThanZero())
5565
_disposable.Pop().Dispose();
5666
}
67+
68+
GC.SuppressFinalize(this);
5769
}
5870

5971
/// <summary>

src/DomainCommonExtensions/DataTypeExtensions/ObjectExtensions.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -178,7 +178,7 @@ public static IDictionary<string, object> ToDictionary(this object obj)
178178
/// <param name="source">Input source object</param>
179179
/// <returns></returns>
180180
/// <remarks></remarks>
181-
public static string ToString(this object source)
181+
public static string ToSafeString(this object source)
182182
{
183183
if (source.IsDbNull() || source.IsNull())
184184
return null;

src/DomainCommonExtensions/DataTypeExtensions/StringExtensions.cs

Lines changed: 33 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1931,9 +1931,9 @@ public static string RemovePeriodValue(this string source, string periodDelimite
19311931
if (source.EndsWith(periodDelimiter + period).IsTrue())
19321932
{
19331933
var idx = source.IndexOf(periodDelimiter + period, StringComparison.Ordinal);
1934-
1935-
return idx == -1
1936-
? source
1934+
1935+
return idx == -1
1936+
? source
19371937
: source.Remove(idx, (periodDelimiter + period).Length);
19381938
}
19391939
else return source;
@@ -2064,5 +2064,35 @@ public static string RemoveEndChars(this string source, int length)
20642064

20652065
return source.Substring(0, source.Length - length);
20662066
}
2067+
2068+
/// -------------------------------------------------------------------------------------------------
2069+
/// <summary>
2070+
/// A string extension method that query if 'source' is missing or contains any values form params.
2071+
/// </summary>
2072+
/// <param name="source">Source string.</param>
2073+
/// <param name="checkValues">A variable-length parameters list containing check values.</param>
2074+
/// <returns>
2075+
/// True if missing or any, false if not.
2076+
/// </returns>
2077+
/// =================================================================================================
2078+
public static bool IsMissingOrAny(this string source, params string[] checkValues)
2079+
{
2080+
return source.IsMissing() || checkValues.NotNull().Contains(source);
2081+
}
2082+
2083+
/// -------------------------------------------------------------------------------------------------
2084+
/// <summary>
2085+
/// A string extension method that if 'source' is missing or contains any values form params.
2086+
/// If is true, then execute action.
2087+
/// </summary>
2088+
/// <param name="source">Source string.</param>
2089+
/// <param name="execAction">The execute action.</param>
2090+
/// <param name="checkValues">A variable-length parameters list containing check values.</param>
2091+
/// =================================================================================================
2092+
public static void IfIsMissingOrAny(this string source, Action execAction, params string[] checkValues)
2093+
{
2094+
if (source.IsMissingOrAny(checkValues))
2095+
execAction();
2096+
}
20672097
}
20682098
}

src/DomainCommonExtensions/Helpers/IniFileHelper.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -112,7 +112,7 @@ public class IniFileHelper
112112
/// The class instance.
113113
/// </summary>
114114
/// =================================================================================================
115-
public static IniFileHelper Instance = new IniFileHelper();
115+
public static readonly IniFileHelper Instance = new IniFileHelper();
116116

117117
/// -------------------------------------------------------------------------------------------------
118118
/// <summary>

src/DomainCommonExtensions/Helpers/RandomHelper.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,7 @@ namespace DomainCommonExtensions.Helpers
3131
/// A random helper.
3232
/// </summary>
3333
/// =================================================================================================
34-
public class RandomHelper
34+
public sealed class RandomHelper
3535
{
3636
/// -------------------------------------------------------------------------------------------------
3737
/// <summary>

0 commit comments

Comments
 (0)