TryAtSoftware.Extensions.Collections 1.1.2-alpha.5

Prefix Reserved
This is a prerelease version of TryAtSoftware.Extensions.Collections.
There is a newer version of this package available.
See the version list below for details.
dotnet add package TryAtSoftware.Extensions.Collections --version 1.1.2-alpha.5                
NuGet\Install-Package TryAtSoftware.Extensions.Collections -Version 1.1.2-alpha.5                
This command is intended to be used within the Package Manager Console in Visual Studio, as it uses the NuGet module's version of Install-Package.
<PackageReference Include="TryAtSoftware.Extensions.Collections" Version="1.1.2-alpha.5" />                
For projects that support PackageReference, copy this XML node into the project file to reference the package.
paket add TryAtSoftware.Extensions.Collections --version 1.1.2-alpha.5                
#r "nuget: TryAtSoftware.Extensions.Collections, 1.1.2-alpha.5"                
#r directive can be used in F# Interactive and Polyglot Notebooks. Copy this into the interactive tool or source code of the script to reference the package.
// Install TryAtSoftware.Extensions.Collections as a Cake Addin
#addin nuget:?package=TryAtSoftware.Extensions.Collections&version=1.1.2-alpha.5&prerelease

// Install TryAtSoftware.Extensions.Collections as a Cake Tool
#tool nuget:?package=TryAtSoftware.Extensions.Collections&version=1.1.2-alpha.5&prerelease                

Quality Gate Status Reliability Rating Security Rating Maintainability Rating Vulnerabilities Bugs

Core validation

About the project

TryAtSoftware.Extensions.Collection is a library containing extension methods that should simplify some common operations with collections.

About us

Try At Software is a software development company based in Bulgaria. We are mainly using dotnet technologies (C#, ASP.NET Core, Entity Framework Core, etc.) and our main idea is to provide a set of tools that can simplify the majority of work a developer does on a daily basis.

Getting started

Installing the package

In order to use this library, you need to install the corresponding NuGet package beforehand. The simplest way to do this is to either use the NuGet package manager, or the dotnet CLI.

Using the NuGet package manager console within Visual Studio, you can install the package using the following command:

Install-Package TryAtSoftware.Extensions.Collections

Or using the dotnet CLI from a terminal window:

dotnet add package TryAtSoftware.Extensions.Collections

Data structures

Bitmask

This is a specialized data structure designed for efficient manipulation of sequence of bits throughout bitwise operations. The proposed implementation can be used to work with bitmasks of various length (especially with long bitmasks).

Initialization

The Bitmask constructor accepts two parameters - the length of the bitmask and a boolean indicating the initial state of all bits - whether they should be set or unset.

Bitmask bitmaskWithZeros = new Bitmask(8, initializeWithZeros: true); // 00000000
Bitmask bitmaskWithOnes = new Bitmask(8, initializeWithZeros: false); // 11111111
Manually setting bits

The Set method can be used to set the bit at a given position. It accepts a single parameter - the zero-based position of the bit that should be set.

The SetAll method can be used to set all bits.

Bitmask bitmask = new Bitmask(8, initializeWithZeros: true); // 00000000
        
bitmask.Set(0); // 10000000
bitmask.Set(3); // 10010000

bitmask.Set(0); // The bit is already set - nothing will be changed.

bitmask.SetAll(); // 11111111

// Invalid cases - position is out of range
bitmask.Set(-1); // Exception will be thrown!
bitmask.Set(8); // Exception will be thrown!
bitmask.Set(100); // Exception will be thrown!
Manually unsetting bits

The Unset method can be used to unset the bit at a given position. It accepts a single parameter - the zero-based position of the bit that should be unset.

The UnsetAll method can be used to unset all bits.

Bitmask bitmask = new Bitmask(8, initializeWithZeros: false); // 11111111
        
bitmask.Unset(1); // 10111111
bitmask.Unset(2); // 10011111

bitmask.Unset(2); // The bit is not set - nothing will be changed.

bitmask.UnsetAll(); // 00000000

// Invalid cases - position is out of range
bitmask.Unset(-1); // Exception will be thrown!
bitmask.Unset(8); // Exception will be thrown!
bitmask.Unset(100); // Exception will be thrown!
Checking the status of a bit

The IsSet method can be used to check the status of a bit at a given position. It accepts a single parameter - the zero-based position of the bit whose status should be checked.

Bitmask bitmask = new Bitmask(8, initializeWithZeros: true);
        
// Set the bits at even positions.
for (int i = 0; i < bitmask.Length; i += 2) bitmask.Set(i);

bool firstBitIsSet = bitmask.IsSet(0); // True
bool secondBitIsSet = bitmask.IsSet(1); // False

// Invalid cases - position is out of range
bitmask.IsSet(-1); // Exception will be thrown!
bitmask.IsSet(8); // Exception will be thrown!
bitmask.IsSet(100); // Exception will be thrown!
Executing bitwise operations

Bitwise operations can be executed by using the corresponding operators. They can come in handy whenever it is required to work over a group of bits collectively, rather than individually. The produced result will be a Bitmask instance as well.

The operations bitwise-and, bitwise-or and exclusive-or do not require both of the operands to have the same length. You can visualize this by padding the shorter bitmask with enough trailing zeros.

For .NET 7 or above the Bitmask type implements the IBitwiseOperators<Bitmask, Bitmask, Bitmask> and IShiftOperators<Bitmask, int, Bitmask> interfaces.

Bitmask bitmask1 = new Bitmask(8, initializeWithZeros: true);
Bitmask bitmask2 = new Bitmask(8, initializeWithZeros: true);

// Set the corresponding bits so the first bitmask looks like this: 11010100
bitmask1.Set(0); bitmask1.Set(1); bitmask1.Set(3); bitmask1.Set(5);

// Set the corresponding bits so the second bitmask looks like this: 01100101
bitmask2.Set(1); bitmask2.Set(2); bitmask2.Set(5); bitmask2.Set(7);

Bitmask andResult = bitmask1 & bitmask2; // 01000100
Bitmask orResult = bitmask1 | bitmask2; // 11110101
Bitmask xorResult = bitmask1 ^ bitmask2; // 10110001

Bitmask leftShiftResult = bitmask1 << 3; // 10100000
Bitmask rightShiftResult = bitmask2 >> 2; // 00011001
// NOTE: Arithmetic and logical right shifts are equivalent.

Bitmask notResult1 = ~bitmask1; // 00101011
Bitmask notResult2 = ~bitmask2; // 10011010
Executing in-place bitwise operations

We can use in-place bitwise operations in order to save up some memory whenever there is no need for the operation's result to be a new Bitmask instance. For this purpose, the InPlaceAnd, InPlaceOr and InPlaceXor methods can be used.

In-place bitwise operations require both of the bitmasks to have the exact same length.

Bitmask bitmask1 = new Bitmask(8, initializeWithZeros: true);
Bitmask bitmask2 = new Bitmask(8, initializeWithZeros: true);
Bitmask bitmask3 = new Bitmask(8, initializeWithZeros: true);
Bitmask bitmask4 = new Bitmask(8, initializeWithZeros: true);

// Set the corresponding bits so the first bitmask looks like this: 11010100
bitmask1.Set(0); bitmask1.Set(1); bitmask1.Set(3); bitmask1.Set(5);

// Set the corresponding bits so the second bitmask looks like this: 01100101
bitmask2.Set(1); bitmask2.Set(2); bitmask2.Set(5); bitmask2.Set(7);

// Set the corresponding bits so the third bitmask looks like this: 01001011
bitmask3.Set(1); bitmask3.Set(4); bitmask3.Set(6); bitmask3.Set(7);

// Set the corresponding bits so the fourth bitmask looks like this: 11000010
bitmask4.Set(0); bitmask4.Set(1); bitmask4.Set(6);

bitmask1.InPlaceAnd(bitmask2); // bitmask1 changes to 01000100; bitmask2 remains unchanged
bitmask2.InPlaceOr(bitmask3); // bitmask2 changes to 01101111; bitmask3 remains unchanged
bitmask3.InPlaceXor(bitmask4); // bitmask3 changes to 10001001; bitmask4 remains unchanged
Find the position of the most significant set bit

The FindMostSignificantSetBit method can be used to find the position of the most significant (left-most) set bit. If there are no set bits, the returned value will be -1.

Bitmask bitmask = new Bitmask(8, initializeWithZeros: true);
        
// Set the corresponding bits so the second bitmask looks like this: 01001000
bitmask.Set(1); bitmask.Set(4);

var position1 = bitmask.FindMostSignificantSetBit(); // 1

bitmask.Unset(1); // 00001000
var position2 = bitmask.FindMostSignificantSetBit(); // 4

bitmask.Unset(4); // 00000000
var position3 = bitmask.FindMostSignificantSetBit(); // -1
Find the position of the most significant unset bit

The FindMostSignificantUnsetBit method can be used to find the position of the most significant (left-most) unset bit. If there are no unset bits, the returned value will be -1.

Bitmask bitmask = new Bitmask(8, initializeWithZeros: false);

// Unset the corresponding bits so the second bitmask looks like this: 11101101
bitmask.Unset(3); bitmask.Unset(6);

var position1 = bitmask.FindMostSignificantUnsetBit(); // 3

bitmask.Set(3); // 11111101
var position2 = bitmask.FindMostSignificantUnsetBit(); // 6

bitmask.Set(6); // 11111111
var position3 = bitmask.FindMostSignificantUnsetBit(); // -1
Find the position of the least significant set bit

The FindLeastSignificantSetBit method can be used to find the position of the least significant (right-most) set bit. If there are no set bits, the returned value will be -1.

Bitmask bitmask = new Bitmask(8, initializeWithZeros: true);
        
// Set the corresponding bits so the second bitmask looks like this: 01001000
bitmask.Set(1); bitmask.Set(4);

var position1 = bitmask.FindLeastSignificantSetBit(); // 4

bitmask.Unset(4); // 01000000
var position2 = bitmask.FindLeastSignificantSetBit(); // 1

bitmask.Unset(1); // 00000000
var position3 = bitmask.FindLeastSignificantSetBit(); // -1
Find the position of the least significant unset bit

The FindLeastSignificantUnsetBit method can be used to find the position of the least significant (right-most) unset bit. If there are no unset bits, the returned value will be -1.

Bitmask bitmask = new Bitmask(8, initializeWithZeros: false);

// Unset the corresponding bits so the second bitmask looks like this: 11101101
bitmask.Unset(3); bitmask.Unset(6);

var position1 = bitmask.FindLeastSignificantUnsetBit(); // 6

bitmask.Set(6); // 11101111
var position2 = bitmask.FindLeastSignificantUnsetBit(); // 3

bitmask.Set(3); // 11111111
var position3 = bitmask.FindLeastSignificantUnsetBit(); // -1
Count the number of set or unset bits

The CountSetBits and CountUnsetBits methods can be used to count the number of set or unset bits, respectively.

Bitmask bitmask = new Bitmask(8, initializeWithZeros: true);

int setBitsCount1 = bitmask.CountSetBits(); // 0
int unsetBitsCount1 = bitmask.CountUnsetBits(); // 8

bitmask.Set(2); // 00100000
int setBitsCount2 = bitmask.CountSetBits(); // 1
int unsetBitsCount2 = bitmask.CountUnsetBits(); // 7

bitmask.Set(3); // 00110000
int setBitsCount3 = bitmask.CountSetBits(); // 2
int unsetBitsCount3 = bitmask.CountUnsetBits(); // 6
Check if two bitmasks share some common bits

Of course, for this purpose we could use the bitwise-and operation like this: (a & b).FindMostSignificantSetBit() != -1, or (a & b).FindLeastSignificantSetBit() != -1, or (a & b).CountSetBits() > 0, etc. However, all of these solutions have on major downside - either we have to sacrifice one of the bitmasks (by using in-place bitwise operations), or we must allocate additional memory.

Conveniently enough, the HasCommonSetBitsWith method solves these two problems.

This method is commutative, i.e. a.HasCommonSetBitsWith(b) will always be equivalent to b.HasCommonSetBitsWith(a).

Bitmask bitmask1 = new Bitmask(8, initializeWithZeros: true);
Bitmask bitmask2 = new Bitmask(8, initializeWithZeros: true);
Bitmask bitmask3 = new Bitmask(8, initializeWithZeros: true);

// Set the corresponding bits so the first bitmask looks like this: 10101010
bitmask1.Set(0); bitmask1.Set(2); bitmask1.Set(4); bitmask1.Set(6);

// Set the corresponding bits so the second bitmask looks like this: 00001111
bitmask2.Set(4); bitmask2.Set(5); bitmask2.Set(6); bitmask2.Set(7);

// Set the corresponding bits so the third bitmask looks like this: 11110000
bitmask3.Set(0); bitmask3.Set(1); bitmask3.Set(2); bitmask3.Set(3);

var result1 = bitmask1.HasCommonSetBitsWith(bitmask2); // True
var result2 = bitmask1.HasCommonSetBitsWith(bitmask3); // True
var result3 = bitmask2.HasCommonSetBitsWith(bitmask3); // False

Collection extensions

OrEmptyIfNull

This is an extension method that will return an empty enumerable if the extended one was null. The main use case is to prevent unnecessary exceptions whenever a null enumerable should not be treated differently than an empty enumerable.

Let's look at this example:

IEnumerable<int> numbers = /* initialization... */;

// 1. Iterating an `IEnumerable<T>` instance
if (numbers != null)
{
    foreach (int num in numbers) { /* Do something */ }
}

// 2. Passing an `IEnumerable<T>` instance to other methods
string text;
if (numbers == null) text = string.Empty;
else text = string.Join(", ", numbers);

It can be improved like this:

IEnumerable<int> numbers = /* initialization... */;

// 1. Iterating an `IEnumerable<T>` instance
foreach (int num in numbers.OrEmptyIfNull()) { /* Do something */ }

// 2. Passing an `IEnumerable<T>` instance to other methods
string text = string.Join(", ", numbers.OrEmptyIfNull());

IgnoreNullValues

This is an extension method that will return a new enumerable containing all values from the extended one that are not null in the same order. The main use case is to reduce the amount of conditions when iterating a collection of elements.

Let's look at this example:

IEnumerable<string> words = /* initialization... */;

if (words != null)
{
    foreach (string w in words)
    {
        if (w == null) continue;

        /* Do something */
    }
}

It can be improved like this:

IEnumerable<string> words = /* initialization... */;

foreach (string w in words.OrEmptyIfNull().IgnoreNullValues())
{
    // Do something
}

IgnoreNullOrWhitespaceValues

This is an extension method that will return a new enumerable containing all string values from the extended one that are not null or empty and do not consist of whitespace characters only in the same order. The main use case is to reduce the amount of conditions when iterating a collection of string elements.

Let's look at this example:

IEnumerable<string> words = /* initialization... */;

if (words != null)
{
    foreach (string w in words)
    {
        if (string.IsNullOrWhitespace(w)) continue;

        /* Do something */
    }
}

It can be improved like this:

IEnumerable<string> words = /* initialization... */;

foreach (string w in words.OrEmptyIfNull().IgnoreNullOrWhitespaceValues())
{
    // Do something
}

IgnoreDefaultValues

This is an extension method that will return a new enumerable containing all values from the extended one that do not equal the default one in the same order. The main use case is to reduce the amount of conditions when iterating a collection of elements.

Let's look at this example:

IEnumerable<Guid> identifiers = /* initialization... */;

if (identifiers != null)
{
    foreach (Guid id in identifiers)
    {
        if (id == Guid.Empty) continue;

        /* Do something */
    }
}

It can be improved like this:

IEnumerable<Guid> identifiers = /* initialization... */;

foreach (Guid id in identifiers.OrEmptyIfNull().IgnoreDefaultValues())
{
    // Do something
}

SafeWhere

This is an extension method that can be used to filter the elements of the extended enumerable safely in terms of the nullability of the predicate.

Let's look at this example:

IEnumerable<Guid> identifiers = /* initialization... */;

if (identifiers != null)
{
    Predicate<Guid> predicate = /* initialization... */;
    if (predicate != null) identifiers = identifiers.Where(predicate);

    foreach (Guid id in identifiers)
    {
        /* Do something */
    }
}

It can be improved like this:

IEnumerable<Guid> identifiers = /* initialization... */;
Predicate<Guid> predicate = /* initialization... */;

foreach (Guid id in identifiers.OrEmptyIfNull().SafeWhere(predicate))
{
    // Do something
}

ConcatenateWith

This is an extension method that can be used to concatenate two collections safely in terms of their nullability. Let's look at this example:

IEnumerable<int> a = /* initialization... */;
IEnumerable<int> b = /* initialization... */;

IEnumerable<int> concatenated;
if (a == null && b == null) concatenated = Enumerable.Empty<int>();
else if (a == null) concatenated = b;
else if (b == null) concatenated = a;
else
{
    List<int> tempConcatenated = new List<int>();
    foreach (int el in a) tempConcatenated.Add(el);
    foreach (int el in b) tempConcatenated.Add(el);

    concatenated = tempConcatenated;
}

It can be improved like this:

IEnumerable<int> a = /* initialization... */;
IEnumerable<int> b = /* initialization... */;

IEnumerable<int> concatenated = a.ConcatenateWith(b);

Union

This extension method will produce the union of multiple IEnumerable<T> instances.

Example:

HashSet<int> a = new HashSet<int> { 1, 2, 3 };
HashSet<int> b = new HashSet<int> { 2, 3, 4 };
HashSet<int> c = new HashSet<int> { 3, 4, 5 };

HashSet<int>[] allSets = new [] { a, b, c };
HashSet<int> union = allSets.Union(); // This will contain: 1, 2, 3, 4, 5

AsReadOnlyCollection

Use this method to easily construct an IReadOnlyCollection<T> instance from the extended collection. This extension method is safe in terms of the nullability of the extended collection.

Let's look at this example:

IEnumerable<Guid> identifiers = /* initialization... */;

IReadOnlyCollection<Guid> readOnlyCollection;
if (identifiers == null) readOnlyCollection = new List<Guid>().AsReadOnly();
else readOnlyCollection = identifiers.ToList().AsReadOnly();

It can be improved like this:

IEnumerable<Guid> identifiers = /* initialization... */;
IReadOnlyCollection<Guid> readOnlyCollection = identifiers.AsReadOnlyCollection();

Dictionary extensions

OrEmptyIfNull

This is an extension method that will return an empty dictionary if the extended one was null. The main use case is to prevent unnecessary exceptions whenever a null dictionary should not be treated differently than an empty dictionary.

Let's look at this example:

IDictionary<int, int> numbersMap = /* initialization... */;

// 1. Iterating an `IDictionary<TKey, TValue>` instance
if (numbersMap != null)
{
    foreach (int (num, count) in numbers) { /* Do something */ }
}

// 2. Passing an `IDictionary<TKey, TValue>` instance to other methods
string text;
if (numbers == null) text = string.Empty;
else text = string.Join(", ", numbers.Select(x => $"{x.Key}: {x.Value}"));

It can be improved like this:

IDictionary<int, int> numbersMap = /* initialization... */;

// 1. Iterating an `IDictionary<TKey, TValue>` instance
foreach (int (num, count) in numbersMap.OrEmptyIfNull()) { /* Do something */ }

// 2. Passing an `IDictionary<TKey, TValue>` instance to other methods
string text = string.Join(", ", numbersMap.OrEmptyIfNull().Select(x => $"{x.Key}: {x.Value}"));

EnsureValue

Use this method to ensure that a value within a dictionary exists for the requested key. In order to use this extension method, the TValue type should be a type that exposes an empty constructor. If the extended dictionary contains the requested key and a value associated with it, that value will be returned. Else, a new instance of the TValue type will be created, saved within the dictionary for the requested key and returned by the method.

Let's look at this example:

IDictionary<string, HashSet<int>> wordsMap = /* initialization... */;

string key = "Hello, world!";
if (!wordsMap.ContainsKey(key)) wordsMap[key] = new HashSet<int>();
wordsMap[key].Add(1);

It can be improved like this:

IDictionary<string, HashSet<int>> wordsMap = /* initialization... */;

string key = "Hello, world!";
HashSet<int> set = wordsMap.EnsureValue(key);
set.Add(1);

MapSafely

Use this method to form a mapping between the selected keys and values for each element in the extended collection. This extension method is safe in terms of what happens whenever two elements have the same key - no exception is thrown and the original value remains unchanged.

IEnumerable<int> numbers = /* initialization... */;

// The following line may throw an exception if `numbers` contains the same number twice.
Dictionary<int, int> doubleValues = numbers.ToDictionary(x => x, x => x * 2);

It can be improved like this:

IEnumerable<int> numbers = /* initialization... */;
IDictionary<int, int> doubleValues = numbers.MapSafely(x => x, x => x * 2);

AsReadOnlyDictionary

Use this method to easily construct an IReadOnlyDictionary<TKey, TValue> instance from the extended dictionary. This extension method is safe in terms of the nullability of the extended dictionary.

Let's look at this example:

IDictionary<int, int> numbersMap = /* initialization... */;

IReadOnlyDictionary<int, int> readOnlyDictionary;
if (numbersMap == null) readOnlyDictionary = new ReadOnlyDictionary<int, int>(new Dictionary<int, int>());
else readOnlyDictionary = new ReadOnlyDictionary<int, int>(numbersMap);

It can be improved like this:

IDictionary<int, int> numbersMap = /* initialization... */;
IReadOnlyDictionary<int, int> readOnlyDictionary = numbersMap.AsReadOnlyDictionary();
Product Compatible and additional computed target framework versions.
.NET net5.0 was computed.  net5.0-windows was computed.  net6.0 was computed.  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 is compatible.  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. 
.NET Core netcoreapp3.0 was computed.  netcoreapp3.1 was computed. 
.NET Standard netstandard2.1 is compatible. 
MonoAndroid monoandroid was computed. 
MonoMac monomac was computed. 
MonoTouch monotouch was computed. 
Tizen tizen60 was computed. 
Xamarin.iOS xamarinios was computed. 
Xamarin.Mac xamarinmac was computed. 
Xamarin.TVOS xamarintvos was computed. 
Xamarin.WatchOS xamarinwatchos was computed. 
Compatible target framework(s)
Included target framework(s) (in package)
Learn more about Target Frameworks and .NET Standard.
  • .NETStandard 2.1

    • No dependencies.
  • net7.0

    • No dependencies.

NuGet packages (4)

Showing the top 4 NuGet packages that depend on TryAtSoftware.Extensions.Collections:

Package Downloads
TryAtSoftware.Extensions.Reflection

This is an internal package used to make work with reflection cleaner and easier than ever before.

TryAtSoftware.Equalizer

This package should be used to make your assertion process in tests cleaner and easier than ever before.

TryAtSoftware.CleanTests

This package should be used to generate multiple combinations for a test case (according to the applied setup) and thus make the process of writing tests cleaner and easier than ever before.

TryAtSoftware.Extensions.DependencyInjection

This is an internal package containing extension methods and utility components that should simplify some common operations with dependency injection.

GitHub repositories

This package is not used by any popular GitHub repositories.

Version Downloads Last updated
1.1.2 977 11/15/2023
1.1.2-alpha.6 90 9/8/2023
1.1.2-alpha.5 84 9/4/2023
1.1.2-alpha.4 81 9/4/2023
1.1.2-alpha.3 79 9/1/2023
1.1.2-alpha.2 88 8/30/2023
1.1.2-alpha.1 80 8/25/2023
1.1.0 801 1/1/2023
1.0.0 5,271 12/16/2022
1.0.0-alpha.2 264 1/29/2022
1.0.0-alpha.1 409 12/11/2021