Gapotchenko.FX 2024.1.3

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

// Install Gapotchenko.FX as a Cake Tool
#tool nuget:?package=Gapotchenko.FX&version=2024.1.3                

Overview

Gapotchenko.FX is the main module of Gapotchenko.FX toolkit. Coincidently, they have identical names.

The module was started by creating its first building block ArrayEqualityComparer back in 2014.

Sure enough, .NET provided a similar Enumerable.SequenceEqual method (System.Linq) that allowed to check two sequences for equality, however it was (and is) very limited:

  • It is slow, and puts a pressure on GC by allocating iterator objects
  • It does not treat null arguments well
  • It does not provide an implementation of IEqualityComparer<T> interface. Good luck trying to make something like Dictionary<byte[], string>.

Many years had passed until it became clear that original platform maintainer is not going to solve that.

As you can imagine, this whole situation gave an initial spark to Gapotchenko.FX project.

What if we have an ArrayEqualityComparer that does the job out of the box? What if it does the job in the fastest possible way by leveraging the properties of host CPU and platform?

Gapotchenko.FX provides the exact solution:

using Gapotchenko.FX;
using System;

var a1 = new byte[] { 1, 2, 3 };
var a2 = new byte[] { 1, 2, 3 };
bool f = ArrayEqualityComparer.Equals(a1, a2);
Console.WriteLine(f);

And what about Dictionary<byte[], string> scenario? It's easy now:

var map = new Dictionary<byte[], string>(ArrayEqualityComparer<byte>.Default);

var key1 = new byte[] { 1, 2, 3 };
var key2 = new byte[] { 110, 230, 36 };

map[key1] = "Easy";
map[key2] = "Complex";

Console.WriteLine(map[key1]);

Functional Influence

Gapotchenko.FX has a strong influence from functional languages and paradigms, so it's important to keep that in mind when studying Gapotchenko.FX module.

Some concepts may seem a bit odd at first look. However, they allow to reap the great benefits. Let's see how and why that happens.

Optional Values

.NET provides a notion of nullable values. For example, a nullable int value:

int? x = null;
if (!x.HasValue)
	x = 10;
Console.WriteLine(x);

But what if we want to do that with a reference type like string? Actually, we can:

string s = null;
if (s == null)
	s = "Test";
Console.WriteLine(s);

Unfortunately, the scheme breaks at the following example:

class Deployment
{
	string m_CachedHomeDir;

	public string HomeDir
	{
		get
		{
			if (m_CachedHomeDir == null)
				m_CachedHomeDir = Environment.GetEnvironmentVariable("PRODUCT_HOME");
			return m_CachedHomeDir;
		}
	}
}

If PRODUCT_HOME environment variable is not set (e.g. its value is null), then GetEnvironmentVariable method will be called again and again diminishing the value of provided caching.

To make this scenario work as designed, we should use an Optional<T> value provided by Gapotchenko.FX. Like so:

using Gapotchenko.FX;

class Deployment
{
	Optional<string> m_CachedHomeDir;

	public string HomeDir
	{
		get
		{
			if (!m_CachedHomeDir.HasValue)
				m_CachedHomeDir = Environment.GetEnvironmentVariable("PRODUCT_HOME");
			return m_CachedHomeDir.Value;
		}
	}
}

Optional values are pretty common in functional languages. And they are simple enough to grasp. But let's move to a more advanced topic - a notion of emptiness.

Notion of Emptiness

Functional style is very similar to Unix philosophy. There are tools, they do their job and they do it well. Those Unix tools can be easily combined into more complex pipelines by redirecting inputs and outputs to form a chain.

Functional programming is no different. There are primitives, and they can be combined to quickly achieve the goal. Due to the fact that underlying primitives are well-written and have no side effects, the combined outcome also tends to be excellent.

Let's take a look at the notion of emptiness provided by the Empty class from Gapotchenko.FX assembly.

The basic thing it does is nullifying. Say, we have the following code:

using Gapotchenko.FX;

class Deployment
{
	public string HomeDir
	{
		get
		{
			if (!m_CachedHomeDir.HasValue)
				m_CachedHomeDir = Environment.GetEnvironmentVariable("PRODUCT_HOME");
			return m_CachedHomeDir.Value;
		}
	}

	Optional<string> m_CachedHomeDir;
}

It's all good, but in real world the PRODUCT_HOME environment variable may be set to an empty string "" on a machine of some customer.

Let's improve the code to handle that condition:

using Gapotchenko.FX;

class Deployment
{
	public string HomeDir
	{
		get
		{
			if (!m_CachedHomeDir.HasValue)
			{
				string s = Environment.GetEnvironmentVariable("PRODUCT_HOME");
				if (string.IsNullOrEmpty(s))
				{
					// Treat an empty string as null. The value is absent.
					s = null;
				}
				m_CachedHomeDir = s;
			}
			return m_CachedHomeDir.Value;
		}
	}

	Optional<string> m_CachedHomeDir;
}

It does the job but that's a lot of thought and code.

We can do better with Empty.Nullify primitive:

using Gapotchenko.FX;

class Deployment
{
	public string HomeDir
	{
		get
		{
			if (!m_CachedHomeDir.HasValue)
				m_CachedHomeDir = Empty.Nullify(Environment.GetEnvironmentVariable("PRODUCT_HOME"));
			return m_CachedHomeDir.Value;
		}
	}

	Optional<string> m_CachedHomeDir;
}

A simple one-liner. We combine Empty.Nullify and Optional<T> primitives in order to get a quick, sound result.

Lazy Evaluation

Most .NET languages employ eager evaluation model. But sometimes it may be beneficial to perform lazy evaluation.

.NET comes pre-equipped with Lazy<T> primitive that does a decent job. However, during the years of extensive Lazy<T> usage it became evident that there are a few widespread usage scenarios where it becomes an overkill.

First of all, Lazy<T> is a class, even in cases where it might be a struct. That means an additional pressure on GC. Secondly, Lazy<T> employs a sophisticated concurrency model where you can select the desired thread-safety level. That means an additional bookkeeping of state and storage, and thus fewer inlining opportunities for JIT.

Gapotchenko.FX extends the .NET lazy evaluation model by providing the new LazyEvaluation<T> primitive. LazyEvaluation<T> is a struct, so it has no memory allocation burden. It does not provide thread safety; though a thread-safe variant does exist in a form of EvaluateOnce<T> primitive.

The sample below demonstrates a typical usage scenario for LazyEvaluation<T>:

using Gapotchenko.FX;

class Deployment
{
	public string HomeDir => m_HomeDir.Value;

	LazyEvaluation<string> m_HomeDir = new(
		() => Empty.Nullify(Environment.GetEnvironmentVariable("PRODUCT_HOME")));
}

Or as a local variable:

using Gapotchenko.FX;

class Program
{
	public static void Main()
	{	
		var homeDir = LazyEvaluation.Create(
			() => Empty.Nullify(Environment.GetEnvironmentVariable("PRODUCT_HOME")));
		// ...
		// Use 'homeDir' value somewhere in the code.
	}
}

On a side note, just look how small our Deployment class became now. Using the functional composition of primitives, we were able to achieve a tiny, reliable and maintainable implementation.

Polyfills

A tagline of Gapotchenko.FX project says "A .NET Polyfill to the Future". But what does it really mean? A couple of things.

First of all, Gapotchenko.FX closes the gaps in original .NET design by providing the missing functionality.

Lazy Polyfill

For example, Lazy<T> class has to be constructed with a new keyword, like so: new Lazy<string>(() => ...). It's a no-brainer for simple types like string. But for custom types it quickly gets clunky:

new Lazy<ICom2PropertyPageDisplayService>(() => ...)

The good news is Gapotchenko.FX allows you to do a better job here:

using Gapotchenko.FX;

Lazy.Create(() => ...)

Gapotchenko.FX provides a static Lazy class that contains a bunch of methods for Lazy<T> instantiation. It allows to leverage the type inference mechanism provided by some .NET languages like C#. It immediately translates into less typing for you on a daily basis.

Secondly, Gapotchenko.FX provides some implementations from future versions of .NET.

HashCode Polyfill

For example, HashCode struct first appeared in .NET Core 2.1. It allows to quickly combine various hash code sources into the final value with a minimal probability of collisions. A very decent thing that was never backported to conventional .NET Framework.

Gapotchenko.FX provides HashCode so you can use it in your projects right now. It even goes further than that by providing extension methods that are likely to appear in .NET in the future (yes, we own a Cassandra's magic ball):

  • SequenceCombine<T>(IEnumerable<T> source)
  • AddRange<T>(IEnumerable<T> source)

Ex Classes

Some Gapotchenko.FX polyfill functionality cannot be packed into existing .NET structures because they don't provide enough extensibility points. In that case, Gapotchenko.FX provides so called "Ex" classes, where "Ex" is an abbreviation of "Extended". For example:

  • HashCodeEx
  • LazyInitializerEx

Summary

As you can see, there are many little things that shape the productive environment into one you can immediately employ, and reap the benefit in no-time.

Gapotchenko FX Ark

Project Manifesto | More on RAD

Supported Target Frameworks

  • .NET 5.0+
  • .NET Core 2.0+
  • .NET Standard 2.0+
  • .NET Framework 4.6.1+

Commonly Used Types

  • Gapotchenko.FX.ArrayEqualityComparer
  • Gapotchenko.FX.Optional
  • Gapotchenko.FX.LazyEvaluation
  • Gapotchenko.FX.UriQueryBuilder

Other Modules

Let's continue with a look at some other modules provided by Gapotchenko.FX:

Or look at the full list of modules.

Product Compatible and additional computed target framework versions.
.NET net5.0 is compatible.  net5.0-windows was computed.  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 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 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.  net9.0 is compatible. 
.NET Core netcoreapp2.0 was computed.  netcoreapp2.1 is compatible.  netcoreapp2.2 was computed.  netcoreapp3.0 is compatible.  netcoreapp3.1 was computed. 
.NET Standard netstandard2.0 is compatible.  netstandard2.1 is compatible. 
.NET Framework net461 is compatible.  net462 was computed.  net463 was computed.  net47 was computed.  net471 is compatible.  net472 is compatible.  net48 was computed.  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 (27)

Showing the top 5 NuGet packages that depend on Gapotchenko.FX:

Package Downloads
Dotnet.Script.Core

A cross platform library allowing you to run C# (CSX) scripts with support for debugging and inline NuGet packages. Based on Roslyn.

Gapotchenko.FX.Threading

Provides complementary primitives for multithreaded and asynchronous programming in .NET.

Gapotchenko.FX.Diagnostics.Process

Provides extended functionality for process manipulation.

Gapotchenko.FX.Text

Provides primitives for string and text manipulation.

Gapotchenko.FX.Diagnostics.CommandLine

Provides primitives for command line manipulation.

GitHub repositories (2)

Showing the top 2 popular GitHub repositories that depend on Gapotchenko.FX:

Repository Stars
dotnet-script/dotnet-script
Run C# scripts from the .NET CLI.
RobinKa/netprints
Visual programming for .NET inspired by Unreal Engine's Blueprints
Version Downloads Last updated
2024.1.3 1,119 11/10/2024
2022.2.7 103,478 5/1/2022
2022.2.5 7,777 5/1/2022
2022.1.4 7,725 4/6/2022
2021.2.21 6,726 1/21/2022
2021.2.20 6,562 1/17/2022
2021.1.5 16,096 7/6/2021
2020.2.2-beta 2,516 11/21/2020
2020.1.15 7,612 11/5/2020
2020.1.9-beta 2,585 7/14/2020
2020.1.8-beta 2,579 7/14/2020
2020.1.7-beta 2,560 7/14/2020
2020.1.1-beta 2,886 2/11/2020
2019.3.7 4,583 11/4/2019
2019.2.20 3,966 8/13/2019
2019.1.151 29,694 3/30/2019