Raccoon.Ninja.Tools
1.4.0
See the version list below for details.
dotnet add package Raccoon.Ninja.Tools --version 1.4.0
NuGet\Install-Package Raccoon.Ninja.Tools -Version 1.4.0
<PackageReference Include="Raccoon.Ninja.Tools" Version="1.4.0" />
paket add Raccoon.Ninja.Tools --version 1.4.0
#r "nuget: Raccoon.Ninja.Tools, 1.4.0"
// Install Raccoon.Ninja.Tools as a Cake Addin #addin nuget:?package=Raccoon.Ninja.Tools&version=1.4.0 // Install Raccoon.Ninja.Tools as a Cake Tool #tool nuget:?package=Raccoon.Ninja.Tools&version=1.4.0
Racoon Ninja Tools
Description
This is a collection of helpers and tools I find useful enough to reuse in multiple projects. I hope this can help other people too. 😃
The idea of the package is to be lightweight and without external dependencies as much as possible. Right now, the project is fully standalone.
Changelog
Check the changelog for the latest updates.
Features
Deterministic Guid
The Guid5
class provides a method to generate deterministic GUIDs (UUID v5) based on the input arguments.
This means that the same set of input arguments will always produce the same GUID, which can be useful for scenarios
where consistent identifiers are needed across different systems or runs.
Example Usage
using Raccoon.Ninja.Tools.Uuid;
using System;
class Program
{
static void Main()
{
// Example with one argument
Guid guid1 = Guid5.NewGuid("example");
Console.WriteLine(guid1);
// Example with multiple arguments
Guid guid2 = Guid5.NewGuid("example", 123, true);
Console.WriteLine(guid2);
// Example with different types of arguments
Guid guid3 = Guid5.NewGuid("example", DateTime.Now);
Console.WriteLine(guid3);
}
}
Performance
Based on the benchmark results, generating a deterministic GUID using the Guid5
class is significantly slower than
creating a regular GUID (UUID v4). Here are some key points:
- Creating a regular GUID (
RegularGuid
) takes approximately 69.41 nanoseconds. - Generating a GUID with one argument (
Guid5WithOneArg
) takes approximately 558 nanoseconds, which is about 6 times slower than creating a regular GUID. - The performance decreases further as more arguments are added or when longer strings are used.
For example, generating a GUID with four arguments including a string of 10,000 chars
(
Guid5WithFourArgsMixedIncLongString
).
While the Guid5
class provides the benefit of deterministic GUIDs, it comes with a performance cost compared to
generating regular GUIDs.
Keep in mind that we're talking about nanoseconds here, so the performance difference may not be significant, depending on your application, and you have the benefit of deterministic GUIDs.
You can execute the benchmarks yourself by running the
Guid5Benchmarks
class in theRaccoon.Ninja.Tools.Benchmark.Tests
project.
BenchmarkDotNet v0.14.0, Windows 10 (10.0.19045.4780/22H2/2022Update) Intel Core i7-9750H CPU 2.60GHz, 1 CPU, 12 logical and 6 physical cores .NET SDK 8.0.401 [Host] : .NET 8.0.8 (8.0.824.36612), X64 RyuJIT AVX2 DefaultJob : .NET 8.0.8 (8.0.824.36612), X64 RyuJIT AVX2
Method | Mean | Error | StdDev | Median | Ratio | RatioSD | Rank | Gen0 | Allocated | Alloc Ratio |
---|---|---|---|---|---|---|---|---|---|---|
RegularGuid | 88.35 ns | 3.445 ns | 10.16 ns | 88.18 ns | 1.01 | 0.17 | 1 | - | - | NA |
Guid5WithOneArg | 558.56 ns | 14.021 ns | 41.34 ns | 548.36 ns | 6.41 | 0.88 | 2 | 0.0353 | 224 B | NA |
Guid5WithTwoArgsMixed | 612.78 ns | 15.601 ns | 46.00 ns | 625.46 ns | 7.03 | 0.97 | 2 | 0.0477 | 304 B | NA |
Guid5WithTwoArgsOnlyStrings | 598.57 ns | 18.596 ns | 54.83 ns | 593.49 ns | 6.87 | 1.01 | 2 | 0.0477 | 304 B | NA |
Guid5WithThreeArgsMixed | 779.63 ns | 19.215 ns | 56.66 ns | 756.28 ns | 8.94 | 1.22 | 4 | 0.0629 | 400 B | NA |
Guid5WithThreeArgsOnlyStrings | 665.40 ns | 17.160 ns | 50.60 ns | 673.00 ns | 7.63 | 1.06 | 3 | 0.0534 | 336 B | NA |
Guid5WithFourArgsMixedIncLongString | 6,577.32 ns | 198.268 ns | 584.60 ns | 6,485.69 ns | 75.44 | 11.02 | 6 | 0.5417 | 3408 B | NA |
Guid5WithFourArgsOnlyStringsIncLongString | 6,308.70 ns | 167.784 ns | 494.71 ns | 6,168.23 ns | 72.36 | 10.12 | 6 | 0.5341 | 3352 B | NA |
Guid5WithLongString | 5,741.92 ns | 168.282 ns | 496.18 ns | 5,824.81 ns | 65.85 | 9.52 | 5 | 0.1907 | 1208 B | NA |
List Extensions
The ListExtensions
class provides several useful extension methods for working with lists and other enumerable collections.
SafeAll<T>
Determines whether all elements of a sequence satisfy a condition safely.
Parameters:
source
(IEnumerable<T>): An enumerable to test.predicate
(Func<T, bool>): A function to test each element for a condition.
Returns:
- bool: True if every element of the source sequence passes the test in the specified predicate. If source is empty or null, returns false.
Example Usage:
var numbers = new List<int> { 1, 2, 3, 4, 5 };
bool allEven = numbers.SafeAll(n => n % 2 == 0); // Output: False
var emptyList = new List<int>();
bool allEvenEmpty = emptyList.SafeAll(n => n % 2 == 0); // Output: False
List<int> nullList = null;
bool allEvenNull = nullList.SafeAll(n => n % 2 == 0); // Output: False
var numbers2 = new List<int> { 2, 4, 6, 8 };
bool allEven2 = numbers2.SafeAll(n => n % 2 == 0); // Output: True
ForEachWithIndex<T>
Returns an iterable list containing every item and its index.
Parameters:
source
(IEnumerable<T>): The target enumerable collection.
Returns:
- IEnumerable<(int index, T item)>: An enumerable containing tuples with the index and item.
Example Usage:
var list = new List<string> { "a", "b", "c" };
foreach (var (index, item) in list.ForEachWithIndex())
{
Console.WriteLine($"Index: {index}, Item: {item}");
}
ContainsCaseInsensitive
Checks if the source contains the specified string, ignoring case.
Parameters:
source
(IEnumerable<string>): The source of strings to search.containsText
(string): The string to search for.nullValuesAreErrors
(bool): If true, null values in the source will be treated as errors and will not match the search string. If false, null values in the source will be ignored.
Returns:
- bool: True if the source contains the specified string (case-insensitive); otherwise, false.
Example Usage:
var list = new List<string> { "Hello", "world" };
bool containsHello = list.ContainsCaseInsensitive("hello");
Console.WriteLine(containsHello); // Output: True
Replace<T>
Replaces the first occurrence of an object in the source.
Parameters:
source
(IList<T>): The source list.oldObj
(T): The old object to be replaced.newObj
(T): The new object that will replace the old one.
Returns:
- bool: True if the object is replaced; false if the object is not found in the source.
Example Usage:
var list = new List<int> { 1, 2, 3 };
bool replaced = list.Replace(2, 4);
Console.WriteLine(replaced); // Output: True
Console.WriteLine(string.Join(", ", list)); // Output: 1, 4, 3
HasElements<T>
Determines whether the specified collection has any elements.
Parameters:
list
(ICollection<T>): The collection to check for elements.
Returns:
- bool: True if the collection is not null and contains one or more elements; otherwise, false.
Example Usage:
var numbers = new List<int> { 1, 2, 3 };
bool hasElements = numbers.HasElements(); // Output: True
var emptyList = new List<int>();
bool hasElements = emptyList.HasElements(); // Output: False
List<string> nullList = null;
bool hasElements = nullList.HasElements(); // Output: False
Shuffle<T>
Shuffles the list in place using the Fisher-Yates algorithm.
Parameters:
list
(IList<T>): The list to shuffle.
Example Usage:
var numbers = new List<int> { 1, 2, 3, 4, 5 };
numbers.Shuffle();
// numbers is now shuffled, e.g., { 3, 1, 5, 2, 4 }
Random<T>
Gets a random item from the list.
Parameters:
list
(IList<T>): The list to get a random item from.
Returns:
- T: A random item from the list.
Example Usage:
var numbers = new List<int> { 1, 2, 3, 4, 5 };
int randomItem = numbers.Random();
// randomItem is now one of the elements in the list, e.g., 3
PopLast<T>
Removes and returns the last item from the list.
Parameters:
list
(IList<T>): The list to remove the last item from.
Returns:
- T: The last item from the list.
Example Usage:
var numbers = new List<int> { 1, 2, 3, 4, 5 };
int lastItem = numbers.PopLast();
// lastItem is now 5, and numbers is now { 1, 2, 3, 4 }
PopFirst<T>
Removes and returns the first item from the list.
Parameters:
list
(IList<T>): The list to remove the first item from.
Returns:
- T: The first item from the list.
Example Usage:
var numbers = new List<int> { 1, 2, 3, 4, 5 };
int firstItem = numbers.PopFirst();
// firstItem is now 1, and numbers is now { 2, 3, 4, 5 }
IndexOfMax<T>
Gets the index of the maximum element in the list.
Parameters:
list
(IList<T>): The list to find the maximum element in.
Returns:
- int: The index of the maximum element in the list.
Example Usage:
var numbers = new List<int> { 1, 3, 2, 5, 4 };
int maxIndex = numbers.IndexOfMax();
// maxIndex is now 3, as the maximum element is 5 at index 3
IndexOfMin<T>
Gets the index of the minimum element in the list.
Parameters:
list
(IList<T>): The list to find the minimum element in.
Returns:
- int: The index of the minimum element in the list.
Example Usage:
var numbers = new List<int> { 1, 3, 2, 5, 4 };
int minIndex = numbers.IndexOfMin();
// minIndex is now 0, as the minimum element is 1 at index 0
RemoveDuplicates<T>
Removes duplicates from the list while preserving order.
Parameters:
list
(IList<T>): The list to remove duplicates from.
Example Usage:
var numbers = new List<int> { 1, 2, 2, 3, 4, 4, 5 };
numbers.RemoveDuplicates();
// numbers is now { 1, 2, 3, 4, 5 }
String Extensions
The StringExtensions
class provides several useful extension methods for working with strings.
Minify
Minifies a text by replacing spaces, tabs, and line breaks with a single space.
Parameters:
bigText
(string): The text to be minified.
Returns:
- string: The minified text.
Example Usage:
string text = @"This is a test.
New line.";
string minifiedText = text.Minify();
Console.WriteLine(minifiedText); // Output: "This is a test. New line."
StripAccents
Removes all diacritics (accents) from a string.
Parameters:
text
(string): The text from which to remove diacritics.
Returns:
- string: The text without diacritics.
Example Usage:
string text = "Café";
string strippedText = text.StripAccents();
Console.WriteLine(strippedText); // Output: "Cafe"
OnlyDigits
Removes everything that is not a digit from a string.
Parameters:
text
(string): The target string.
Returns:
- string: A string containing only digits.
Example Usage:
string text = "Phone: 123-456-7890";
string digits = text.OnlyDigits();
Console.WriteLine(digits); // Output: "1234567890"
DateTime Extensions
The DateTimeExtensions
class provides several useful extension methods for working with DateTime
objects.
DaysSince
Calculates the number of days since the specified date.
Parameters:
date
(DateTime): The date to calculate the days since.currentDate
(DateTime?): The current date to compare against. If null (not provided), the current date and time will be used. (Default: current datetime)allowMixedDateTimeKind
(bool): Indicates whether to allow mixed DateTimeKind values between the date and currentDate. (Default: false)
Returns:
- int: The number of days between the specified date and the current date.
Example Usage:
var pastDate = new DateTime(2020, 1, 1);
var daysSince = pastDate.DaysSince(); // Calculates days since January 1, 2020 to today
Operation Result
The Result<TPayload>
class represents the result of an operation, which can either be a success with a payload or a
failure with an error. This class provides methods to map and process the result based on its success or failure state.
It's a somewhat functional approach to handling operation results, not fully adhering to the functional programming paradigm but providing some of its benefits.
Example Usage
using Raccoon.Ninja.Tools.OperationResult;
using Raccoon.Ninja.Tools.OperationResult.ResultError;
class Program
{
static void Main()
{
// Example mapping result.
var result = DoSomething();
result.Map(
success => Console.WriteLine($"Success! Here's the result: {success}"), // Only executed if successful
error => Console.WriteLine($"Oh no! It failed! Error: {error.ErrorMessage}") // Only executed if failed
);
// Example processing result and getting an object back.
var result2 = DoSomethingElseAndReturnPayload();
var processedPayload = failureResult.Process(
success => $"Processed: {success}", // Only executed if successful
failure => $"Failed: {failure.ErrorMessage}" // Only executed if failed
);
Console.WriteLine(processedPayload);
}
}
Performance
Instantiation
Quick update here. I caved in and changed from class
to readonly struct
. I think the performance benefits, thread
safety, and immutability are worth it. Thankfully, this won't affect anyone, because the contracts are the same.
Below are the results of the benchmark tests for instantiating Classes, Struct, Readonly Struct, and Records. I'm aware that they are not the best, but it's good enough for a brief comparison.
Method | Mean | Error | StdDev | Median | Rank | Gen0 | Allocated |
---|---|---|---|---|---|---|---|
NewClass | 5.7458 ns | 0.2447 ns | 0.7216 ns | 5.6876 ns | 2 | 0.0051 | 32 B |
NewStruct | 0.0688 ns | 0.0320 ns | 0.0938 ns | 0.0008 ns | 1 | - | - |
NewReadonlyStruct | 0.0795 ns | 0.0341 ns | 0.0990 ns | 0.0370 ns | 1 | - | - |
NewRecord | 5.6249 ns | 0.2198 ns | 0.6482 ns | 5.5565 ns | 2 | 0.0051 | 32 B |
To no one's surprise, instantiating a struct
or readonly struct
is by far the fastest option and won't allocate any
memory.
Usage
For the usage benchmark test, I create the following scenarios:
ThrowExceptionOnError
: When an error occurs, an exception is thrown and captured by the caller;ReturnResultOnError
: When an error occurs, the result is returned to the caller (instead of throwing an exception);ReturnNullOnExceptionCaught
: When an error occurs, the result is returned asnull
to the caller;ReturnImplicitResultOnExceptionCaught
: When an error occurs, the exception is implicitly converted to the Result type and returned to the caller;ReturnExplicitResultOnExceptionCaught
: When an error occurs, the exception is explicitly converted to the Result type and returned to the caller;ReturnSuccessList
: When the operation is successful, a list of string is returned to the caller;ReturnSuccessResultList
: When the operation is successful, a list of string is returned as a Result type to the caller. The converstion is done implicitly.
BenchmarkDotNet v0.14.0, Windows 10 (10.0.19045.4780/22H2/2022Update)
Intel Core i7-9750H CPU 2.60GHz, 1 CPU, 12 logical and 6 physical cores
.NET SDK 8.0.401
[Host] : .NET 8.0.8 (8.0.824.36612), X64 RyuJIT AVX2
DefaultJob : .NET 8.0.8 (8.0.824.36612), X64 RyuJIT AVX2
Method | Mean | Error | StdDev | Median | Ratio | RatioSD | Rank | Gen0 | Allocated | Alloc Ratio |
---|---|---|---|---|---|---|---|---|---|---|
ThrowExceptionOnError | 8,339.272 ns | 211.8029 ns | 614.4788 ns | 8,358.530 ns | 1.005 | 0.11 | 4 | 0.0458 | 344 B | 1.00 |
ReturnResultOnError | 8.545 ns | 0.3371 ns | 0.9834 ns | 8.411 ns | 0.001 | 0.00 | 1 | 0.0051 | 32 B | 0.09 |
ReturnNullOnExceptionCaught | 6,147.746 ns | 121.6040 ns | 318.2166 ns | 6,139.373 ns | 0.741 | 0.07 | 3 | 0.0305 | 224 B | 0.65 |
ReturnImplicitResultOnExceptionCaught | 6,095.540 ns | 159.0650 ns | 461.4763 ns | 5,964.967 ns | 0.735 | 0.08 | 3 | 0.0381 | 256 B | 0.74 |
ReturnExplicitResultOnExceptionCaught | 6,280.372 ns | 124.5711 ns | 332.5055 ns | 6,357.890 ns | 0.757 | 0.07 | 3 | 0.0381 | 256 B | 0.74 |
ReturnSuccessList | 22.468 ns | 0.5101 ns | 0.6451 ns | 22.420 ns | 0.003 | 0.00 | 2 | 0.0140 | 88 B | 0.26 |
ReturnSuccessResultList | 23.845 ns | 0.5110 ns | 1.4074 ns | 23.536 ns | 0.003 | 0.00 | 2 | 0.0140 | 88 B | 0.26 |
In general terms, using the Operation Result to handle the results, in case of errors, is way faster than throwing an exception and uses less memory (because of the overhead involved in the exception handling). So that's a great idea when you don't need details from an exception and just want to communicate an error.
When capturing an unexpected exception, and returning a result (instead of null, empty list or something like that), from the benchmark, it seems like there's no significant difference between the approaches.
For cases of success, the overhead is minimal (about 1.3ns), which isn't really significant, and with no extra memory allocation.
From this, seems like the Operation Result is a good choice for handling errors and returning results in a standardized way.
Other
Shamelessly plugging the link to my site: https://raccoon.ninja
Product | Versions Compatible and additional computed target framework versions. |
---|---|
.NET | net8.0 is compatible. 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. |
-
net8.0
- No dependencies.
NuGet packages
This package is not used by any NuGet packages.
GitHub repositories
This package is not used by any popular GitHub repositories.
## [1.4.0] - Sep, 2024
- Added DateTime extension methods:
- `DaysSince`: Returns the number of days since a given date (default: current date time);