JamSoft.Helpers 1.3.0

dotnet add package JamSoft.Helpers --version 1.3.0
NuGet\Install-Package JamSoft.Helpers -Version 1.3.0
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="JamSoft.Helpers" Version="1.3.0" />
For projects that support PackageReference, copy this XML node into the project file to reference the package.
paket add JamSoft.Helpers --version 1.3.0
#r "nuget: JamSoft.Helpers, 1.3.0"
#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 JamSoft.Helpers as a Cake Addin
#addin nuget:?package=JamSoft.Helpers&version=1.3.0

// Install JamSoft.Helpers as a Cake Tool
#tool nuget:?package=JamSoft.Helpers&version=1.3.0



A collection of general helpers for applications and libraries. The goal is to provide convenience methods and core building blocks. All in a unit tested, cross-platform .NET Standard 2.0 library with minimal dependencies.

.NET Core Coverage Status Nuget GitHub CodeFactor

Table of Contents


string count certain character instances.





Install-Package JamSoft.Helpers -Version 1.2.4


dotnet add package JamSoft.Helpers --version 1.2.4

Package Reference

<PackageReference Include="JamSoft.Helpers" Version="1.2.4" />

Package Reference

paket add JamSoft.Helpers --version 1.2.4


There is a high level of test coverage as shown in the badge, however, at the moment the pipeline executes only on Windows which means some tests cannot be run in this environment. The library has been fully tested on Windows 10, 11, OSX Catalina, MacOS, and Fedora 31.

The following test classes also show basic example implementations and uses of the provided pattern classes.

  • ObserverTests
  • MementoTests
  • MyTestViewModel
  • ATestSettingsClass
  • BTestSettingsClass
  • PersonViewModel

Sample Application

A sample AvaloniaUI application is now also included in the Sample directory.


Rather than getting embroiled in the convoluted and sometimes awkward user settings infrastructure provided by .NET (*.settings), sometimes you just want to store some values in a file, yes?

The issues and additional complications around user.config and strong names can sometimes get in the way. Using the SettingsBase<T> class can bypass this.

Set Up

Create a POCO class containing your settings properties, default values and inherit SettingsBase<T>, such as:

public sealed class MySettings : SettingsBase<MySettings>
    public string ASetting { get; set; } = "A Default Value";

Loading & Access

Now you can load, save and manage your settings at runtime like:

string myDefaultSettingsPath = "C:\Some\location\on\disk";

This call will either load the settings from a file called mysettings.json, the name is automatically taken from the type name, or if no file exists, the defaults are loaded.

You can also load and save from a provided file name instead of deriving from the type name, such as:

string myDefaultSettingsPath = "C:\Some\location\on\disk";
MySettings.Load(myDefaultSettingsPath, "custom-name.json");

You can access the values using the instance:

var theValue = MySettings.Instance.ASetting;


MySettings.Instance.ASetting = theValue;


Saving the settings is a call to the Save() method, like:


This will always save back to the same file the settings were originally loaded from, or if there was no file, the file will be created and the settings saved.


You can easily return back to the defaults by calling the ResetToDefaults() method, like:


This will reset all settings to their default values and immediately write them to disk. If you do not want to write them to disk, simply pass a false to the method.


Dirty Object Tracking

Using the attributes and validators you can track classes with changes, such as view models, in order save new data or update UI state accordingly.

The Interface

There are a number of ways of implementing something like such as decorators and so forth, but to keep this as pluggable as possible this feature makes use of an interface to implement on your validatable classes.

public interface IDirtyMonitoring
    /// <summary>
    /// A flag denoting if the object is dirty
    /// </summary>
    bool IsDirty { get; set; }
    /// <summary>
    /// The object hash value
    /// </summary>
    string? Hash { get; set; }

To create instances of validators in 1.3.0 things have changed a bit. It's no longer a state class and is now instantiated from a factory, such as:

services.RegisterLazySingleton(() => DirtyValidatorFactory.Create());

Or you can simply call the factory directly.

_isDirtyValidator = DirtyValidatorFactory.Create();

Obviously you could also wire this up in your favourite DI container.


First implement the interface on your own classes, such as:

public class PersonViewModel : IDirtyMonitoring
    public string Name { get; set; }
    public string DisplayName { get; set; }
    public bool IsDirty { get; set; }    
    public string? Hash { get; set; }

In this example only the DisplayName property is monitored for changes. After an object has completed being initialised and is in it's "clean" state, validate it.


Now, at any point in time you can validate it again to detect if the class is dirty.

p.DisplayName = "Original";
_isDirtyValidator.Validate(p).IsDirty; // false
p.DisplayName = "SomedifferentValue";
_isDirtyValidator.Validate(p).IsDirty; // true
p.DisplayName = "Original";
_isDirtyValidator.Validate(p).IsDirty; // false

Property & Field Tracking

As of v1.2.0 it is also possible to track which properties in a given object instance have changed. In order to track properties, call the Validate method and pass True as the trackProperties parameter.

_isDirtyValidator.Validate(p, trackProperties:true);

Now that the object, its properties and fields have been validated changes can be reported in more detail. Calls to the ValidatePropertiesAndFields() method will return collections of PropertyInfo and FieldInfo objects, such as:

var (props, fields) = _isDirtyValidator.ValidatePropertiesAndFields(p);

In the example above the props and fields collections contain details of the properties and fields that have changed since the previous validation process.

To restart this validation process on the same instance, simply re-validate it and pass True again.

_isDirtyValidator.Validate(p, true);

Dirty Monitoring Example Usage

public void LoadPeopleFromDataSource()
    var vms = Mapper.Map(_dataService.GetPeople());
    foreach(var vm in vms)
        _isDirtyValidator.Validate(p, trackProperties:true);

public void SaveUiState()
    foreach(var vm in _people)
            // save logic
            var (props, fields) = _isDirtyValidator.ValidatePropertiesAndFields(vm);
            // more granular save logic

Managing Hashes

The property containing the hash store is now exposed (since v1.2.5) so you can better manage the resources in use. You can simple set the property to a new empty collection or clear the existing one.

_isDirtyValidator.ObjectValueHashStore = new();

Or you can perform a complete reset using the provided method with the option of clearing down the cached type data:

_isDirtyValidator.Reset(); // clearTypeInfo:false
// will clear any cached type info


Shuffle Collections

IEnumerable<int> ints = new[] { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 };
IEnumerable<int> shuffledInts = ints.Shuffle();

Or you can provide your own instance of Random.

Random randomiser = new Random();
IEnumerable<int> ints = new[] { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 };
IEnumerable<int> shuffledInts = ints.Shuffle(randomiser);


There are a handful of helper methods and classes for access environment variables on various platforms.

Common Variables

var path = EnvEx.GetVariable(EnvExVariableNames.Path);

Windows Variables

var appData = EnvEx.GetVariable(EnvExWinVariableNames.AppData); // C:\Users\username\AppData\Roaming

OSX Variables

var shell = EnvEx.GetVariable(EnvExOsxVariableNames.Shell); // "/bin/bash"

Linux Variables

var manPath = EnvEx.GetVariable(EnvExLinuxVariableNames.ManPath); // ":"

More variables names are included in the library than are shown above. You can make use of these via helper constants in the following classes:

  • EnvExVariableNames (Common)
  • EnvExWinVariableNames
  • EnvExOsxVariableNames
  • EnvExLinuxVariableNames

Since the EnvEx.GetVariable method just takes a string, any value can be passed, such as:

var envValue = EnvEx.GetVariable("MYVARIABLENAME");

On Window you can also pass a target parameter of type EnvironmentVariableTarget. The default for this is Process as Linux and OSX do not support this parameter. If anything other than Process is passed on a non-Windows platform it will be defaulted to Process to prevent exceptions being raised.


There is a new little class to help digitally sign data with RSA Cryptography. The main class is created via a factory which can be registered in your DI container of choice.

public interface IRsaCryptoFactory

container.Register<IRsaCryptoFactory, RsaCryptoFactory>();

You can then use this factory to obtain instances of the service.

var crypto = cryptoFactory.Create();

There are many overloads of the create method to control how the service is built and how you want it configured.

You should also wrap each use of this within a using statement such as:

using(var crypto = cryptoFactory.Create(_privateKey, _publicKey, HashAlgorithmName.SHA512, RSASignaturePadding.Pkcs1))
   // do your stuff.

Once you have created an instance if you do not provide either of the keys, you can obtain the used keys for storage via the two properties, such as:

var crypto = cryptoFactory.Create();
var publicKey = sut.PublicKey;
var privateKey = sut.PrivateKey;


Convert to HEX

int red = 121;
int green = 155;
int blue = 56;

var hex = Graphics.Colors.ToHex(red, green, blue);

Or you can also use an alpha value.

var hex = Graphics.Colors.ToHex(alpha, red, green, blue);

You can also pass values as an array

var hex = Graphics.Colors.ToHex(new[] { alpha, red, green, blue });

Convert to RGB

int red = 255;
int green = 169;
int blue = 104;

var color = Graphics.Colors.ToRgb("#FFA968");

Convert to ARGB

This can be useful for WPF and XAML which supports an alpha value in the HEX.

int alpha = 255;
int red = 146;
int green = 145;
int blue = 145;

var c = Graphics.Colors.ToArgb("#FF929191");

UI Values

Data Sizes

Converts integer and long values representing data sizes to human readable form

int input = 10000000;
input.ToHumanReadable(); returns "9.54 Mb"

long input = 2000000000000000000;
input.ToHumanReadable(); returns "1.73 Eb"


double input = 3657;
input.ToTimeDisplayFromSeconds() returns "01:00:57"

double input = 3657.12;
input.ToTimeDisplayFromSeconds(withMs: true) returns "01:00:57:120"

TimeSpan input = new TimeSpan(16, 45, 0);
input.GetTime() returns "16:45"


Even or Odd

Even number detection

int value = 2;
var isMyNumberEven = value.IsEvenNumber();


Basic percentage calculations

int value = 500;
int total = 2000;

var percent = value.IsWhatPercentageOf(total) // 25


Hamming Distance

Calculates the number of edits required to go from one string to another must be equal lengths to start

var inputOne = "InputString1";
var inputTwo = "InputString2";

var distance = Distance.GetHammingDistance(inputOne, inputTwo);

Levenshtein Distance

Calculates the number of edits required to go from one string to another

var inputOne = "InputString1";
var inputTwo = "InputString2";

var distance = Distance.GetLevenshteinDistance(inputOne, inputTwo);

Shorten Strings

This method allows you to shorten strings to a predetermined length and pad with ... or any pattern you provide.

string input = "Thisismylongstringthatneedsshortening";
var result = input.DotShortenString(10, 20); // "Thisism...shortening"

string input = "Thisismylongstringthatneedsshortening";
var result = input.DotShortenString(10, 20, ";;;"); // "Thisism;;;shortening"

Remove All Multi-spaces

var input = "  This  has    too  many  spaces   ";
input.RemoveAllMultiSpace(); // " This has too many spaces "
var input = "  This  has    too  many  spaces   ";
input.RemoveAllMultiSpace(trim:true); // "This has too many spaces"
var input = "  This  has    too  many  spaces   ";
input.RemoveAllMultiSpace("--"); // "--This--has--too--many--spaces--"
var input = "  This  has    too  many  spaces   ";
input.RemoveAllMultiSpace(pattern:"--", trim:true) // "This--has--too--many--spaces"

String Compare

string input = "string1";
string pattern = "strinG1";

input.IsExactlySameAs(pattern); // false


XML Encoding Formatting

Adds a strict uppercase UTF-8 to XML declarations

using (var sw = new UppercaseUtf8StringWriter())
    xsSubmit.Serialize(sw, new TestObject { SomeProperty = "SomeValue" });
    xml = sw.ToString();



A very bare bones view model with property changed updates

public abstract class ViewModelBase : INotifyPropertyChanged
    public bool IsEditable ...
    public bool IsBusy ...
    protected virtual bool SetProperty<T>(ref T storage, T value, [CallerMemberName] string propertyName = "")

Mvvm - SuperObservableCollection<T>

An observable collection that mutes change notifications when adding a range of objects and allows sorting

public class SuperObservableCollection<T> : ObservableCollection<T>
    public SuperObservableCollection(IEnumerable<T> coll)

    public void AddRange(IEnumerable<T> list, bool suppressNotifications = true, bool notifiyOnceAllAdded = true)

    public void Sort(bool suppressNotifications = false)
    public void Sort(IComparer<T> comparer, bool suppressNotifications = false)


A very basic implementation of the core bits of the observer pattern

public interface IObservable
    void Attach(IObserver observer);

    void Detach(IObserver observer);

    void Notify();
public interface IObserver
    void Update(IObservable observable);
public abstract class ObservableBase : IObservable


public interface IMemento
    object GetState();
public interface IMementoOwner
    IMemento Save();

    void Restore(IMemento memento);
public class MementoManager
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 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. 
.NET Core netcoreapp2.0 was computed.  netcoreapp2.1 was computed.  netcoreapp2.2 was computed.  netcoreapp3.0 was computed.  netcoreapp3.1 was computed. 
.NET Standard netstandard2.0 is compatible.  netstandard2.1 was computed. 
.NET Framework net461 was computed.  net462 was computed.  net463 was computed.  net47 was computed.  net471 was computed.  net472 was computed.  net48 is compatible.  net481 was computed. 
MonoAndroid monoandroid was computed. 
MonoMac monomac was computed. 
MonoTouch monotouch was computed. 
Tizen tizen40 was computed.  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.

NuGet packages (1)

Showing the top 1 NuGet packages that depend on JamSoft.Helpers:

Package Downloads

A collection of general helpers for AvaloniaUI applications.

GitHub repositories

This package is not used by any popular GitHub repositories.

Version Downloads Last updated
1.3.0 329 5/1/2023
1.2.4 325 3/4/2023
1.2.3 349 2/22/2023
1.2.2 248 2/21/2023
1.2.1 253 2/20/2023
1.2.0 261 2/19/2023
1.1.0 261 2/18/2023
1.0.2 393 8/27/2022
1.0.1 423 6/4/2022
0.4.2 1,301 4/18/2021
0.1.0 902 5/17/2020