wan24-Core
1.25.0
See the version list below for details.
dotnet add package wan24-Core --version 1.25.0
NuGet\Install-Package wan24-Core -Version 1.25.0
<PackageReference Include="wan24-Core" Version="1.25.0" />
paket add wan24-Core --version 1.25.0
#r "nuget: wan24-Core, 1.25.0"
// Install wan24-Core as a Cake Addin #addin nuget:?package=wan24-Core&version=1.25.0 // Install wan24-Core as a Cake Tool #tool nuget:?package=wan24-Core&version=1.25.0
wan24-Core
This core library contains some .NET extensions and boiler plate avoiding helpers. It's designed as core library for a long running process and optimized for that purpose. The code tries to cache agressive whereever it's possible. Types are designed to be thread-safe, if they're likely to be accessed reading/writing from multiple threads (if it's their nature). However, if it's not sure that a types nature is to manage multithreaded access, performance goes before thread safety.
It started as a slender extension method collection and growed up to a larger core utility until today. It's possible that some specialized parts will be splitted into separate libraries in the future, if the core library is getting too big - but the namespace will stay the same.
Some key features:
- Bootstrapping
- Disposable base class for disposable types, which supports asynchronous
disposing
- Dispose attribute for fields/properties which should be disposed automatic when disposing
CancellationOnDispose
cancels a cancellation token when an object is being disposed (or another given cancellation token ws canceled)Cancellations
combines multiple cancellation tokens into one- Type helper (type loading)
- Secure byte and char array, which clears its contents when disposing
- Pool rented array as disposable object (which optionally clears its contents
when disposing; for byte/char arrays just like the
Secure*Array
) - Asynchronous API helper
- Asynchronous fluent API helper
- Byte array extensions
- Endian conversion
- Bit-converter (endian-safe)
- UTF-8/16/32 (little endian) string decoding
- Clearing
- Dictionary extensions
- Merge with string key prefix
- Merge a list with the index as key (and an optional key prefix)
- Char array extensions
- Clearing
- Array helper extensions
- Offset/length validation
- Array pool extensions
- Renting a cleared array
- Enumerable extensions
- Combine enumerables
- Chunk enumerables
- Reflection extensions
- Automatic parameter extension when invoking a method (with DI support)
- Synchronous/asynchronous method invokation
- Automatic constructor invokation using a given parameter set (with DI support)
- Nullability detection
- Get property getter/setter delegates
- Get cached field/property/method info
- Cache for field/property/method info and custom attributes
- Delegate extensions
- Delegate list invokation (with or without return values, with DI support)
- Asynchronous delegate list invokation (with or without return values, with DI support)
- Task extensions
- Result getting of a generic task
- Asynchronous task list awaiting
- Shortcuts for await configurations
- Shortcuts for starting a function as long running task
- Shortcuts for starting a function as task with fair execution by the scheduler
- Add a cancellation token to a task (which can cancel the task awaiter)
- DI helper
- Service provider adoption
- DI object factory delegates
- Asynchronous DI object factory delegates
- Enumeration extensions
- Get enumeration value display string from
DisplayTextAttribute
or usingToString
(fallback) - Determine if all, any or which flags are contained in an enumeration value
- Remove flags of a mixed enumeration value
- Get only flags of a mixed enumeration value
- Value validation
- Get enumeration value display string from
- Number extensions
- Determine if a type is a number
- Determine if a number type is unsigned
- Bit-converter (endian-safe)
- Determine if a number (or any
IComparable
) is within a range
- Numeric bitwise extensions
- Collection extensions
- Add a range of items
- JSON helper
- Exchangeable JSON encoder/decoder delegates (using
System.Text.Json
per default)
- Exchangeable JSON encoder/decoder delegates (using
- JSON extensions
- Encode an object
- Decode from a type
- Decode a string
- Object extensions
- Type conversion
- Determine if a value is within a list of values
- String extensions
- Get UTF-8/16/32 bytes (little endian)
- Parsing
- Generic helper
- Determine if two generic values are equal
- Determine if a value is
null
- Determine if a value is
default
- Determine if a value is
null
ordefault
DateTime
extensions- Determine if a time is within a range
- Determine if a time matches a reference time plus/minus an offset
- Apply an offset to a time base on a reference time
TimeSpanHelper
- Update a timeout
- Queue worker (for actions and/or items)
- Parallel queue worker (for actions and/or items)
ParallelAsync
implementationForEachAsync
with an asynchronous or synchronous input sourceFilterAsync
with an asynchronous or synchronous input source and item filterFilter
for synchronous parallel filtering
- Base class for a hosted worker, which implements the
IHostedService
interface (timed or permanent running) EventThrottle
for throttling event handler callsProcessThrottle
for throttling a processing channelOrderedDictionary<tKey, tValue>
is used for working with indexed key/value pairsTimeout
will count down and raise an event, if not reset before reaching the timeoutILogger
supportIChangeToken
support usingChangeCallback
- Hierarchic configuration using
OverrideableConfig
- Cancellation token awaiter
ObjectPool
for pooling objects (DisposableObjectPool
for disposable types), andBlockingObjectPool
for a strict pool capacity limit(Blocking)StreamPool
,PooledMemoryStream
,PooledTempFileStream
andPooledTempStream
(hosts written data in memory first)ResetEvent
for (a)synchronous event waitingLazyValue<T>
,DisposableLazyValue<T>
,AsyncDisposableLazyValue<T>
andTimeoutValue<T>
for lazy and timeout value servingObjectLockManager<T>
for asynchronous and awaitable object lockingBitmap
for working with bitsDisposableWrapper<T>
for wrapping any (not disposable?) object with theIDisposable
andIAsyncDisposable
interface using custom dispose actions during runtimeDisposableAdapter
for adopting theIDisposableObject
interface from a type which can't extend theDisposableBase
type- Generic object extenions for validating method arguments
- CLI arguments interpreter
- Runtime configuration from CLI arguments
- Fast byte to string and string to byte encoding/decoding (using an URI friendly charset, faster and smaller results than base64 encoding; charset is customizable; encoded data integrity can be validated without decoding; including extensions for numeric type encoding/decoding)
- Collecting periodical statistical values
- Streams
StreamBase
as base class which implements some disposing logicWrapperStream
wraps a base stream and providesLeaveOpen
PartialStream
wraps a part of a base stream (read-only)LengthLimitedStream
ensures a maximum stream length (only writing)MemoryPoolStream
uses anArrayPool<byte>
for storing written dataThrottledStream
throttles reading/writing troughputTimeoutStream
can timeout async reading/writing methodsBlockingBufferStream
for writing to / reading from a buffer blockedHubStream
for forwarding writing operations to multiple target streamsLimitedStream
limits reading/writing/seeking capabilities of a streamZeroStream
reads zero bytes and writes to nowhereCountingStream
counts red/written bytesPauseableStream
is a stream which can temporary pause reading/writingEnumerableStream
streams an enumerable/enumeratorCombinedStream
combines multiple streams into one stream (read-only)SynchronizedStream
synchronizes IO and seekingRandomStream
reads random bytes into the given buffers
- Named mutex helper
GlobalLock
for a synchronous contextGlobalLockAsync
for an asynchronous context
- Retry helper which supports timeout, delay and cancellation
- Asynchronous event
- Stream extensions
- Get the number of remaining bytes until the streams end
- Copy a part of a stream to another stream
- Generic seek
- Write N zero bytes
- Write N random bytes
- Create stream chunks
- Checksum implementation in
ChecksumExtensions
andChecksumTransform
How to get it
This library is available as NuGet package "wan24-Core".
Bootstrapping
The Bootstrapper.Async
method calls all static methods having the
BootstrapperAttribute
. In order to be able to find the methods, it's
required to add the BootstrapperAttribute
to the assembly.
You may also ad the BootstrapperAttribute
to a type and/or the bootstrapper
method, in case the assembly contains multiple of them. In the assembly
attribute you need to set ScanClasses
and/or ScanMethods
to true
in
order to perform a deep scanning during bootstrapping for performance reasons.
The bootstrapper methods may consume parameters which are available from the DI helper. The method may be synchronous or asynchronous. The method can't be defined in a generic class, and it can't be generic itself.
[assembly:Bootstrapper(typeof(YourBootstrapper),nameof(YourBootstrapper.BootstrapperMethod))]
public static class YourBootstrapper
{
public static async Task BootstrapperMethod()
{
// Perform your bootstrapping here
}
}
// Call the bootstrapper somewhere in your apps initialization code
await Bootstrap.Async();
The BootstrapperAttribute
can be initialized with a numeric priority. The
bootstrapper will order the found bootstrapping methods by priority, where the
one with the highest number will be executed first (assembly and type
priorities count, too). At last there's a assembly location, type and method
name sorting. Bootstrapper methods will be executed sequential.
If you give a type and a method name to the assembly BootstrapperAttribute
,
you won't need to add the attribute to the type and the method.
During bootstrapping, the cancellation token which was given to the
Bootstrap.Async
method, can be injected to a bootstrappers method parameters.
After that bootstrapping was done, the Bootstrap.AsyncBootstrapper
will be
called. At last the Bootstrap.OnBootstrap
event will be raised.
During bootstrapping the Bootstrap.IsBooting
property is true
. After
bootstrapping the Bootstrap.DidBoot
property is true
.
The bootstrapper will load all referenced assemblies. If you load an assembly
later, it'll be bootstrapped automatic and added to the TypeHelper
singleton
instance.
Type helper
If you use the TypeHelper.AddTypes
method, the unknown assemblies of the
added types will be added as searchable assemblies automatic.
You may attach to the TypeHelper.OnLoadType
event for handling requests
more dynamic.
The TypeHelper.GetType
method will try Type.GetType
first and fall back to
the helper, if no type was found.
DI helper
In order to make DI (dependency injection) working, you need to
- set a
DiHelper.ServiceProvider
and/or - add
DiHelper.(Async)ObjectFactories
The DiHelper.GetDiObjectAsync
method will try to resolve the request
synchronous, first. But the DiHelper.GetDiObject
won't try asynchronous
object factories.
Mixed enumeration value
A mixed enumeration contains X bits enumeration values, and Y bits flags:
[Flags]
public enum MixedEnum : int
{
None = 0,
Value1 = 1,
Value2 = 2,
Value3 = 3,
...
Flag1 = 1 << 8,
Flag2 = 1 << 9,
FLAGS = Flag1 | Flag2 // Required to identify flags
}
The FLAGS
value helps these extension methods to handle flag values:
MixedEnum value = MixedEnum.Value1 | MixedEnum.Flag1,
valueOnly = value.RemoveFlags(),// == MixedEnum.Value1
flagsOnly = value.OnlyFlags();// == MixedEnum.Flag1
Unsafe code
The library uses unsafe code. If you don't want/need that, you can compile the
library with the NO_UNSAFE
compiler constant to disable any unsafe
operation. Remember to unset the unsafe compiler option, too!
Disposable base class
The DisposableBase
implements the IDisposable
and IAsyncDisposable
interfaces. It provides some helpers and events, and also the
DisposeAttribute
, which can be applied to fields and properties which you
wish to dispose automatic when disposing.
When your type derives from the DisposableBase
, you'll need to implement the
abstract Dispose
method:
protected override Dispose(bool disposing)
{
// Your dispose logic here
}
There are measures to avoid that this method is being called twice.
To implement custom asynchronous disposing:
protected override async Task DisposeCore()
{
// Your dispose logic here
}
In order to make the DisposeAttribute
working, you have to call the
protected method DisposeAttributes
or DisposeAttributesAsync
.
The IsDisposing
property value will be true
as soon as the disposing
process started, and it will never become false
again. The IsDisposed
property value will be true
as soon as the disposing process did finish.
TIP: Use the DisposableBase<T>
base type, if you plan to use the
DisposeAttribute
! This base class will cache the fields/properties once on
initialization to get rid of the reflection overhead which DisposableBase
requires for this feature.
NOTE: The DisposeAttribute
can be applied to byte[]
and char[]
, too,
which will simply call the Clear
extension method on disposing. Another
IEnumerable
will be enumerated for disposable items (recursing!).
Queue worker
using QueueWorker worker = new();
await worker.EnqueueAsync((ct) =>
{
// Do any background action here
});
The QueueWorker
class can be extended as you need it.
The ParallelQueueWorker
requires a number of threads in the constructor,
which defines the degree of parallelism, in which enqueued tasks will be
processed.
Queue item worker
using QueueItemWorker<ItemType> worker = new();
await worker.EnqueueAsync(new ItemType());
The QueueItemWorker<T>
class can be extended as you need it.
The ParallelItemQueueWorker<T>
requires a number of threads in the
constructor, which defines the degree of parallelism, in which enqueued items
will be processed.
ParallelAsync
Using the .NET parallel implementation it's not possible to invoke
asynchronous item handlers. For this you can use the
ParallelAsync.ForEachAsync
method, which uses a parallel item queue worker
in the background for asynchronous processing.
Hosted worker
public class YourHostedWorker : HostedWorkerBase
{
public YourHostedWorker() : base() { }
protected override async Task WorkerAsync()
{
// Perform the service actions here
}
}
The hosted worker implements the IHostedService
interface and can be
extended as you need it.
Timed hosted worker
public class YourHostedWorker : TimedHostedWorkerBase
{
public YourHostedWorker() : base(interval: 500) { }
protected override async Task WorkerAsync()
{
// Perform the service actions here
}
}
This example uses a 500ms timer. Based on the defined timer type, the interval will be processed in different ways:
Default
: Next worker run is now plus the interval (used by default)Exact
: Next worker run is now plus the interval minus the processing duration (used, if the start time of the processing is important)ExactCatchingUp
: AsExact
, but catching up missing processing runs without delay, if a worker run duration exceeds the interval (used, if the number of worker runs is important)
Using the SetTimerAsync
method you can change the timer settings at any
time. If you give the nextRun
parameter, you may set a fixed next run time
(which won't effect the given interval, but just force the service to run at a
specific time for the next time).
NOTE: The nextRun
parameter will also force the service to (re)start!
By setting the property RunOnce
to true
, the service will stop after
running the worker once. In combination with the SetTimerAsync
parameter
nextRun
you can execute the worker at a specific time once.
The hosted worker implements the IHostedService
interface and can be
extended as you need it.
EventThrottle
public class YourType : DisposableBase
{
protected readonly YourEventThrottle EventThrottle;
public YourType() : base() => EventThrottle = new(this);
// This method will raise the OnEvent
public void AnyMethod()
{
RaiseOnEventThrottled();
}
protected override Dispose(bool disposing) => EventThrottle.Dispose();
// Delegate for OnEvent
public delegate void YourTypeEvent_Delegate();
// Event to throttle
public event YourTypeEvent_Delegate? OnEvent;
// Raise the OnEvent using the event throttle
protected void RaiseOnEventThrottled() => EventThrottle.Raise();
// Finally let the event handlers process the event
protected void RaiseOnEvent() => OnEvent?.Invoke();
// Event throttle implementation
public class YourEventThrottle : EventThrottle
{
// Throttle the event handling down to max. one handling per 300ms
public YourEventThrottle(YourType instance) : base(timeout: 300) => Instance = instance;
public YourType Instance { get; }
protected override HandleEvent(DateTime raised, int raisedCount)
{
Instance.RaiseOnEvent();
}
}
}
If AnyMethod
is being called, the event will be forwarded to the event
throttle, which decides to throttle or raise the event. If AnyMethod
was
called three times within 300ms, the first call will be executed in realtime,
while the 2nd and the 3rd call will be sqashed and executed once 300ms after
the 1st call was processed.
This example assumes you're working with a real event - but you may throttle any event (which may not be a real event) using throttling logic.
ProcessThrottle
public class YourProcessThrottle : ProcessThrottle
{
// Throttle to processing one object per second
public YourProcessThrottle() : base(limit: 1, timeout: 1000) { }
// Processing API using a timeout
public async Task<int> ProcessAsync(Memory<bool> items, TimeSpan timeout)
=> await ProcessAsync(items.Length, (count) =>
{
await Task.Yield();
Span<bool> toProcess = items.Span[..count];
items = items[count..];
// Process toProcess
}, timeout);
// Processing API using a cancellation token
public async Task<int> ProcessAsync(Memory<bool> items, CancellationToken token = default)
=> await ProcessAsync(items.Length, (count) =>
{
await Task.Yield();
Span<bool> toProcess = items.Span[..count];
items = items[count..];
// Process toProcess
}, token);
}
The example will throttle the processing to a maximum of one object per
second. Multiple threads may call ProcessAsync
concurrent - processing will
be organized thread-safe.
The return value of ProcessAsync
is the number of objects processed until
timeout or canceled.
The processing delegate shouldn't care about the timeout or if canceled and just process the given number of objects.
NOTE: A usage gap will slide the throttling timer. Example:
The timeout was set to 3 objects per 100ms. Now processing goes like this:
- First processed object on
0ms
will activate the throttling timeout - Next processed object on
10ms
will increase the object throttling counter - Next processed object on
110ms
will reset the throttling timeout and counter (the usage gap of 100ms does exceed the timeout) - Next 2 processed objects on
120ms
will activate the throttle - Next object will have to wait until the throttle was released
- The throttle will be released on
210ms
, which allows the last object to be processed now
In short words: The throttle timer will not reset in an fixed interval, but the interval starts when processing items.
Change token
Implement by extending ChangeToken
:
public class YourObservableType : ChangeToken
{
public YourObservableType() : base()
{
ChangeIdentifier = () => HasChanged;
}
public bool HasChanged => ...;// Return if the object was changed
public void ChangeAction()
{
// Perform changes
InvokeCallbacks();
}
}
Or by using a ChangeToken
instance:
public class YourObservableType : IChangeToken
{
public readonly ChangeToken ChangeToken;
public YourObservableType() => ChangeToken = new(() => HasChanged);
public bool HasChanged => ...;// Return if the object was changed
public void ChangeAction()
{
// Perform changes
ChangeToken.InvokeCallbacks();
}
// Implement the IChangeToken interface using our ChangeToken instance
bool IChangeToken.HasChanged => ChangeToken.HasChanged;
bool IChangeToken.ActiveChangeCallbacks => ChangeToken.ActiveChangeCallbacks;
IDisposable IChangeToken.RegisterChangeCallback(Action<object?> callback, object? state)
=> ChangeToken.RegisterChangeCallback(callback, state);
}
Hierarchic configuration
Assume this configuration hierarchy:
Level | Description |
---|---|
1 | Default values |
2 | User values (can override default values) |
3 | Administrator values (can override default/user values) |
In code:
public sealed class Config : OverrideableConfig<Config>
{
public Config() : base()
{
SubConfig = new(this, new(this));// User values
InitProperties();
}
private Config(Config parent, Config? sub = null) : base(parent)
{
if(sub != null)
{
SubConfig = sub;
sub.ParentConfig = this;
sub.SubConfig = new(sub);// Administrator values
}
InitProperties();
}
// A configuration value
public ConfigOption<string, Config> AnyValue { get; private set; } = null!;
private void InitProperties()
{
AnyValue = ParentConfig == null
// The master option has a default value
? new(this, nameof(AnyValue), canBeOverridden: true, "default")
// No default value for a sub-option
: new(this, nameof(AnyValue));
}
}
Config config = new(),
user = config.SubConfig,
admin = user.SubConfig;
CAUTION: There's no endless-recursion protection for the ParentConfig
or
the SubConfig
properties!
Now users are able to override default values, and administrators are able to override default and/or user values:
// Still the default value
Assert.AreEqual("default", config.AnyValue.FinalValue);
// User overrides the default value
user.AnyValue.Value = "user";
Assert.AreEqual("default", config.AnyValue.Value);
Assert.AreEqual("user", config.AnyValue.FinalValue);
// Administrator overrides the user value
admin.AnyValue.Value = "admin";
Assert.AreEqual("admin", config.AnyValue.FinalValue);
// User can't override the administrator value (but still store his own value
// in case the administrator would unset his value)
user.AnyValue.Value = "test";
Assert.AreEqual("admin", config.AnyValue.FinalValue);
Assert.AreEqual("test", user.AnyValue.Value);
NOTE: Setting an option value is thread-safe.
It's also possible to flip the hierarchy:
Level | Description |
---|---|
1 | Default values |
2 | Administrator values (can define user visible and optional not overrideable values) |
3 | User values (can override overrideable values) |
Using this hierarchy an administrator could also allow or deny overriding values at any time, for example.
The hierarchy depth isn't limited.
Object locking
The ObjectLockManager<T>
helps locking any object during an asynchronous
operation:
ObjectLock ol = await ObjectLockManager<AnyType>.Shared.LockAsync(anyObjectKey);
// A 2nd call to ObjectLockManager<AnyType>.Shared.LockAsync would block until the lock was released
await ol.RunTaskAsync(Task.Run(async () =>
{
// Perform the asynchronous operation here
}));
// ol is disposed already, 'cause the asynchronous operation source task was awaited
// The next ObjectLockManager<AnyType>.Shared.LockAsync call will be processed now, if any
await ol.Task;// To throw any exception during performing the asynchronous operation
If AnyType
implements the IObjectKey
interface, it can be given to the
ObjectLockManager<T>
methods as object argument.
NOTE: ObjectLock
will dispose itself as soon as RunTaskAsync
has been
called, and the given task was completed.
CLI arguments interpreter
There a just a few rules:
- A flag starts with a single dash
- A key for a value (list) starts with a double dash
- Keys/values can be quoted using single or double quotes
- Escape character is the backslash (only applicable in quoted values)
- A quoted value must be escaped for JSON decoding, a backslash must be double escaped
- Double quotes in a quoted value must be escaped always
Example:
"-flag" --key 'value1' value2 --key -value3 '--key2' "value"
For appending the value -value3
to the value list of key
, the value needs
to be added with another --key
key identifier, 'cause it starts with a dash
and could be misinterpreted as a flag (which would result in a parser error).
A CLI app called with these arguments could interpret them easy using the
CliArguments
class:
CliArguments cliArgs = new(args);
Assert.IsTrue(cliArgs["flag"]);
Assert.AreEqual(3, cliArgs.All("key").Count);
Assert.AreEqual("value", cliArgs.Single("key2"));
A --
(double dash) may be interpreted as an empty key name or a flag with
the name -
, based on if a value, which doesn't start with a dash, is
following. Examples:
--
:-
flag-- -
:-
flag (--
and-
are both interpreted as double-
flag (double flags will be combined))-- value
: Empty key with the valuevalue
-- -key
:-
andkey
flags
Keyless arguments will be stored in the KeyLessArguments
list - example:
CliArguments ca = CliArguments.Parse("value1 -flag value2 --key value3");
Assert.AreEqual(2, ca.KeyLessArguments.Count);
Assert.AreEqual("value1", ca.KeyLessArguments[0]);
Assert.AreEqual("value2", ca.KeyLessArguments[1]);
Assert.IsTrue(ca["flag"]);
Assert.IsTrue(ca["key", true]);
Fast byte ↔ string encoding/decoding
base64 is supported everywhere, but it's (relative) slow and produces too much overhead, and uses also URI unfriendly characters. In addition it's also not easy to validate base64, or to determine the encoded/decoded value length.
To fix all of these problems, the ByteEncoding
class implements a fast
encoding, which uses only characters 0-9, a-z, A-Z, dash and underscore and
produces less overhead than base64. The encoded/decoded value length can be
calculated in advance, and it's fast and easy to detect errors in the encoded
data without having to decode it, first.
// In case you want to use a prepared output buffer
int encodedLen = anyByteArray.GetEncodedLength();
// Encoding
char[] encoded = anyByteArray.Encode();
// In case you want to use a prepared output buffer
int decodedLen = encoded.GetDecodedlength();
// Decoding
byte[] decoded = encoded.Decode();
Using extensions numeric values can be en-/decoded on the fly, too. The
special EncodeNumberCompact
extension methods determine the smallest value
matching numeric type before encoding (use DecodeCompactNumber
with the
original numeric type as generic argument for decoding).
NOTE: Encoding an empty array results in an empty string. Encoding 0
results in an empty string, too. Nothing encodes to nothing and decodes to
nothing, too.
If required, the used encoding character map can be customized. You may use any 64 characters long map with unique items.
String parser
Using the Parse
extension method for a string
, you can parse placeholders
into a string and modify the output using (customizable) parser functions:
Dictionary<string, string> data = new()
{
{"name", "value"}
};
Assert.AreEqual("value", "%{name}".Parse(data));
You may setup the default parser data in StringExtensions.ParserEnvironment
.
The given parser data will override defaults.
You can execute as many parser functions on the output as required, separated
using :
:
%{input:func1:func2(param1,param2,...):func3():...}
The first optional segment is always a parser data variable name (if not used,
the sequence starts with a :
to indicate a function call). A function may or
may not have parameters. The result of a function will be provided for the
next function. Available functions:
Function | Syntax | Usage |
---|---|---|
sub |
%{input:sub([offset/length](,[length]))} |
extracts a sub-string |
left |
%{input:left([length])} |
takes X characters from the left |
right |
%{input:right([length])} |
takes X characters from the right |
trim |
%{input:trim} |
removes white-spaces from the value |
discard |
%{input:discard} |
no parameters, discards the current output |
escape_html |
%{input:escape_html} |
escapes the value for use within HTML |
escape_json |
%{input:escape_json} |
escapes the value for use within double quotes (double quotes will be trimmed from the JSON result!) |
escape_uri |
%{input:escape_uri} |
escapes the value for use within an URI |
set |
%{input:set([name])} sets the current output as parser variable with the given name |
|
var |
%{:var([name])} gets a parser data variable value |
|
item |
%{:item([index],[item/name](,[item](,...)))} gets an item from a list (if using a variable name, its value will be splitted using pipe) |
|
prepend |
%{input:prepend([string])} |
prepends a string |
append |
%{input:append([string])} |
appends a string |
insert |
%{input:insert([index],[string])} |
inserts a string at an index |
remove |
%{input:remove([offset/length](,[length]))} |
removes a part (from the left) |
concat |
%{:concat([string],[string](,[string](...))} |
concatenates strings |
join |
%{:join([separator],[string],[string](,...))} |
joins strings |
math |
%{:math([operator],[value1],[value2](,...))} |
performs math |
rx |
%{:rx([group_index]],[name/pattern])} |
exchanges the parser regular expression and content group index for the next parser operations (the next round) |
format |
%{input:format([format])} |
to format a numeric value |
str_format |
%{input:str_format(([value1](,...))} to format the string value |
|
insert_item |
%{input:insert_item([index],[items_name])} |
to insert an item (items will be splitted by pipe) |
remove_item |
%{input:remove_item([index])} |
to remove an item (items will be splitted by pipe) |
sort |
%{input:sort((desc))} |
to sort items |
foreach |
%{input:foreach([name])} |
to parse a parser data value for each item (will be stored in _item ) |
if |
%{input:if([name](,[name]))} |
to parse a parser data value, if the value is 1 (else parse the second given parser data value) |
split |
%{input:split(prefix)} |
to split items by pipe and set them as parser data using the prefix and appending the zero based item index |
range |
%{:range([start],[count])} |
to create a numeric range |
dummy |
%{:dummy(...)} |
does nothing (may be used as comment) |
Available math operators:
Operator | Function |
---|---|
+ |
Summarize |
- |
Substract |
* |
Multiply |
/ |
Divide |
% |
Modulo |
a |
Average |
i |
Minimum |
x |
Maximum |
r |
Round (2nd value is the number of decimals) |
f |
Floor |
c |
Ceiling |
p |
Y power of X (double conversion will be applied) |
= |
Equality (0 is not equal, 1 if equal) |
< |
Lower than (0 is not lower, 1 if lower) |
> |
Greater than (0 is not greater, 1 if greater) |
s |
Change the sign |
Numbers are written in invariant culture float
style. decimal
will be used
as number format.
To create a custom parser function:
StringExtensions["func_name"] = (context) =>
{
// Work with the StringParserContext and return the value to use or set context.Error for error handling
return context.Value;
};
Example:
StringExtensions["upper"] = (context) => context.Value.ToUpper();
Dictionary<string, string> data = new()
{
{"name", "value"}
};
Assert.AreEqual("VALUE", "%{name:upper}".Parse(data));
CAUTION: A placeholder must produce the same result, if it occurs repeated! A repeated placeholder won't be parsed more than once, but being replaced with the result of the first parsed placeholder.
Example:
Dictionary<string, string> data = new()
{
{"name", "value"}
};
string tmpl = "%{name}%{name:len:set(name):discard}%{name}";
Assert.AreEqual("valuevalue", tmpl.Parse(data));
From the logic value5
would be expected. To get value5
, finally, you'll
have to modify the template:
%{name}%{name:len:set(name):discard}%{name:dummy}
TIP: Almost all function parameters may be parser data variable names,
too, if they have a $
prefix. To support that, use the TryGetData
method
of the StringParserContext
, if a parameter value starts with $
.
TIP: To ensure having all required parameters, use the
EnsureValidParameterCount
of the StringParserContext
. The method allows
you to define a number of allowed parameter counts (including zero) and
produces a common error message, if the function call syntax is wrong.
TIP: A custom parser function may change the parser regular expression and
content group by changing Rx
and RxGroup
.
The string parser works recursive. To avoid an endless recursion, the default
parsing round count limit is 3. The current parsing round is accessable trough
the parser data _round
. If a parser function parses a template, the called
parser will work in the current parsing round context and respect the limit,
too. Youmay set another default limit in StringExtensions.ParserMaxRounds
.
The default behavior for errors is to throw an exception. If error throwing was disabled, in case of an error a placeholder will stay in clear text, and a function will return the unaltered value.
You may modify the placeholder declaration by setting another regular
expression to StringExtensions.RxParser
. Group $1
must contain the whole
placeholder, while group $2
is required to contain the inner placeholder
contents (like variable name, function calls, parameters, etc.). There's no
way to customize the inner placeholder content syntax at present. You may also
give a custom regular expression to the Parse
extension method, if you want
an isolated parsing. You can modify the inner content group index by setting
StringExtensions.RxParserGroup
or giving rxGroup
to the Parse
methods.
CAUTION: Be careful with customized parser functions: A mistake could let a manipulated string harm your computer!
Retry helper
RetryInfo<object> result = await RetryHelper.TryActionAsync(
async (currentTry, cancellation) =>
{
// Perform any critical action which may throw or timeout and return a value (or not)
},
maxNumberOfTries: 3,
timeout: TimeSpan.FromSeconds(30),
delay: TimeSpan.FromSeconds(3)
);
// This will throw an exception, if failed, or return the action delegate return value, if succeed
object returnValue = result.ThrowIfFailed();
TryAction*
will try to execute an action for a maximum of N times, optional
having a total timeout, and optional performing a delay after a failed try.
The given action delegate may also return a value, which you can then find in
the RetryInfo<T>.Result
property, if Succeed
is true
.
The RetryInfo<T>
object contains some runtime informations:
- Start, done time and total runtime
- Number of tries processed (a timeout or cancellation may throw before the action is being called)
- Catched exceptions during tries
- If succeed, cancelled or timeout
- The action delegate return value (if any)
NOTE: There's also a synchronous TryAction
method, which supports
timeout and cancellation also.
Asynchronous events
// Example type using an asynchronous event
public class YourType
{
public readonly AsyncEvent<YourType, EventArgs> OnYourEvent;
public YourType() => OnYourEvent = new(this);
public async Task RaiseOnYourEventAsync()
=> await ((IAsyncEvent<YourType, EventArgs>)OnYourEvent).RaiseEventAsync();
}
// An example asynchronous event listener
async Task eventListener(YourType sender, EventArgs e, CancellationToken ct)
{
...
}
// Attach to the event and raise it
YourType obj = new();
Assert.IsFalse(obj.OnYourEvent);
obj.OnYourEvent.Listen(eventListener);
Assert.IsTrue(obj.OnYourEvent);
await obj.RaiseOnYourEventAsync();
// Detach the event listener
obj.OnYourEvent.Detach(eventListener);
Assert.IsFalse(obj.OnYourEvent);
An AsyncEvent<tSender, tArgs>
instance will only export public event
informations and functions like adding/removing event handlers, and if event
handlers are present. For raising the event, you need to use the
RaiseEventAsync
methods which are available from the
IAsyncEvent<tSender, tArgs>
interface.
Timeout, cancellation, synchronous and asynchronous event handlers are
supported. The AsyncEvent<tSender, tArgs>
is designed to be thread-safe,
while multiple threads are allowed to raise the event in parallel.
Checksum
ChecksumExtensions
and ChecksumTransform
allow generating a checksum:
byte[] data = ...,
moreData = ...,
checksum = data.CreateChecksum();
moreData.UpdateChecksum(checksum);
The default checksum length is 8 bytes and needs to be a power of two, if being customized. If you need a numeric value from the checksum bytes:
ulong numericChecksum = checksum.AsSpan().ToULong();
The algorithm uses XOR to modify the checksum bytes, which are zero by default. If the input data is only zero, the checksum will stay at zero. If you use the same input data for a 2nd time, the checksum will be equal to the one from the 1st time.
The ChecksumTransform
is a HashAlgorithm
and can be used as every .NET
implemented hash algorithm (even it's not a hash, but only a checksum!):
byte[] checksum = ChecksumTransform.HashData(data);
You may register the checksum algorithm as "Checksum" using the Register
method:
ChecksumTransform.Register();
Product | Versions Compatible and additional computed target framework versions. |
---|---|
.NET | net6.0 is compatible. net6.0-android was computed. net6.0-ios was computed. net6.0-maccatalyst was computed. net6.0-macos was computed. net6.0-tvos was computed. net6.0-windows was computed. net7.0 was computed. net7.0-android was computed. net7.0-ios was computed. net7.0-maccatalyst was computed. net7.0-macos was computed. net7.0-tvos was computed. net7.0-windows was computed. net8.0 was computed. net8.0-android was computed. net8.0-browser was computed. net8.0-ios was computed. net8.0-maccatalyst was computed. net8.0-macos was computed. net8.0-tvos was computed. net8.0-windows was computed. |
-
net6.0
- Microsoft.Extensions.Hosting.Abstractions (>= 7.0.0)
- Microsoft.Extensions.Logging.Abstractions (>= 7.0.1)
NuGet packages (14)
Showing the top 5 NuGet packages that depend on wan24-Core:
Package | Downloads |
---|---|
Stream-Serializer-Extensions
Serializer extensions for .NET Stream objects. |
|
wan24-Compression
Compression helper |
|
wan24-Crypto
Crypto helper |
|
wan24-Crypto-BC
Bouncy Castle adoption to wan24-Crypto |
|
wan24-Compression-LZ4
LZ4 adoption for wan24-Compression |
GitHub repositories
This package is not used by any popular GitHub repositories.
Version | Downloads | Last updated |
---|---|---|
2.45.0 | 90 | 11/16/2024 |
2.44.0 | 105 | 11/10/2024 |
2.43.0 | 96 | 11/3/2024 |
2.42.0 | 453 | 10/27/2024 |
2.41.0 | 82 | 10/21/2024 |
2.40.0 | 90 | 10/20/2024 |
2.39.0 | 100 | 9/29/2024 |
2.38.0 | 657 | 9/21/2024 |
2.37.0 | 117 | 9/15/2024 |
2.36.0 | 300 | 9/8/2024 |
2.35.0 | 230 | 8/24/2024 |
2.34.0 | 679 | 8/16/2024 |
2.33.0 | 233 | 8/4/2024 |
2.32.0 | 427 | 7/13/2024 |
2.31.0 | 394 | 7/6/2024 |
2.30.0 | 185 | 6/29/2024 |
2.29.0 | 352 | 6/22/2024 |
2.28.0 | 327 | 6/15/2024 |
2.27.0 | 100 | 6/8/2024 |
2.26.0 | 123 | 6/1/2024 |
2.25.0 | 130 | 5/26/2024 |
2.24.0 | 136 | 5/20/2024 |
2.23.0 | 175 | 5/11/2024 |
2.22.0 | 338 | 5/9/2024 |
2.21.0 | 142 | 5/5/2024 |
2.20.0 | 164 | 4/28/2024 |
2.19.0 | 160 | 4/20/2024 |
2.18.1 | 161 | 4/14/2024 |
2.18.0 | 347 | 4/12/2024 |
2.17.0 | 126 | 4/7/2024 |
2.16.0 | 218 | 3/30/2024 |
2.15.1 | 124 | 3/30/2024 |
2.15.0 | 115 | 3/30/2024 |
2.14.0 | 141 | 3/24/2024 |
2.13.0 | 151 | 3/17/2024 |
2.12.0 | 192 | 3/15/2024 |
2.11.0 | 156 | 3/10/2024 |
2.10.1 | 130 | 3/10/2024 |
2.10.0 | 232 | 3/9/2024 |
2.9.2 | 317 | 3/2/2024 |
2.9.1 | 135 | 3/2/2024 |
2.9.0 | 168 | 3/2/2024 |
2.8.0 | 141 | 2/25/2024 |
2.7.1 | 130 | 2/25/2024 |
2.7.0 | 116 | 2/25/2024 |
2.6.0 | 277 | 2/24/2024 |
2.5.0 | 120 | 2/20/2024 |
2.4.0 | 127 | 2/18/2024 |
2.3.2 | 190 | 2/17/2024 |
2.3.1 | 127 | 2/17/2024 |
2.3.0 | 124 | 2/17/2024 |
2.2.0 | 402 | 1/20/2024 |
2.1.0 | 128 | 12/23/2023 |
2.0.0 | 203 | 12/17/2023 |
1.43.0 | 164 | 11/27/2023 |
1.42.0 | 298 | 11/11/2023 |
1.41.2 | 124 | 11/4/2023 |
1.41.1 | 123 | 11/4/2023 |
1.41.0 | 121 | 11/4/2023 |
1.40.0 | 267 | 10/29/2023 |
1.39.0 | 279 | 10/21/2023 |
1.38.2 | 150 | 10/15/2023 |
1.38.1 | 322 | 10/14/2023 |
1.38.0 | 139 | 10/14/2023 |
1.37.0 | 141 | 10/13/2023 |
1.36.0 | 327 | 10/7/2023 |
1.35.0 | 215 | 10/1/2023 |
1.34.0 | 204 | 9/27/2023 |
1.33.0 | 127 | 9/20/2023 |
1.32.1 | 312 | 9/19/2023 |
1.32.0 | 117 | 9/19/2023 |
1.31.1 | 173 | 9/16/2023 |
1.31.0 | 188 | 9/16/2023 |
1.30.1 | 274 | 9/10/2023 |
1.30.0 | 139 | 9/10/2023 |
1.29.0 | 323 | 9/3/2023 |
1.28.0 | 144 | 8/26/2023 |
1.27.0 | 146 | 8/19/2023 |
1.26.0 | 166 | 8/5/2023 |
1.25.1 | 276 | 7/30/2023 |
1.25.0 | 266 | 7/30/2023 |
1.24.0 | 390 | 7/22/2023 |
1.23.0 | 139 | 7/9/2023 |
1.22.0 | 144 | 6/25/2023 |
1.21.0 | 165 | 6/24/2023 |
1.20.0 | 162 | 6/17/2023 |
1.19.0 | 207 | 6/11/2023 |
1.18.2 | 160 | 6/10/2023 |
1.18.1 | 160 | 6/9/2023 |
1.18.0 | 349 | 6/8/2023 |
1.17.0 | 147 | 6/4/2023 |
1.16.0 | 491 | 6/3/2023 |
1.15.0 | 320 | 5/29/2023 |
1.14.0 | 153 | 5/29/2023 |
1.13.0 | 155 | 5/28/2023 |
1.12.0 | 326 | 5/27/2023 |
1.11.0 | 148 | 5/24/2023 |
1.10.0 | 147 | 5/23/2023 |
1.9.0 | 136 | 5/22/2023 |
1.8.2 | 319 | 5/20/2023 |
1.8.1 | 159 | 5/20/2023 |
1.8.0 | 153 | 5/20/2023 |
1.7.1 | 174 | 5/13/2023 |
1.7.0 | 209 | 5/11/2023 |
1.6.1 | 1,522 | 4/26/2023 |
1.6.0 | 393 | 4/25/2023 |
1.5.0 | 423 | 4/22/2023 |
1.4.0 | 168 | 4/22/2023 |
1.3.0 | 306 | 4/16/2023 |
1.2.0 | 251 | 4/10/2023 |
1.1.0 | 191 | 4/7/2023 |
1.0.1 | 206 | 4/1/2023 |