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
<PackageReference Include="Gapotchenko.FX" Version="2024.1.3" />
paket add Gapotchenko.FX --version 2024.1.3
#r "nuget: Gapotchenko.FX, 2024.1.3"
// 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 likeDictionary<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.
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:
- âž´ Gapotchenko.FX
- Gapotchenko.FX.AppModel.Information
- Gapotchenko.FX.Collections
- Gapotchenko.FX.Console
- Gapotchenko.FX.Data
- Gapotchenko.FX.Diagnostics
- Gapotchenko.FX.IO
- Gapotchenko.FX.Linq
- Gapotchenko.FX.Math
- Gapotchenko.FX.Memory
- Gapotchenko.FX.Security.Cryptography
- Gapotchenko.FX.Text
- Gapotchenko.FX.Threading
- Gapotchenko.FX.Tuples
Or look at the full list of modules.
Product | Versions 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. |
-
.NETCoreApp 2.1
- System.Runtime.CompilerServices.Unsafe (>= 4.5.2)
-
.NETCoreApp 3.0
- No dependencies.
-
.NETFramework 4.6.1
- Microsoft.Bcl.HashCode (>= 1.1.1)
- System.Memory (>= 4.5.3)
- System.Runtime.CompilerServices.Unsafe (>= 4.5.2)
- System.Runtime.InteropServices.RuntimeInformation (>= 4.3.0)
- System.ValueTuple (>= 4.5.0)
-
.NETFramework 4.7.1
- Microsoft.Bcl.HashCode (>= 1.1.1)
- System.Memory (>= 4.5.3)
- System.Runtime.CompilerServices.Unsafe (>= 4.5.2)
-
.NETFramework 4.7.2
- Microsoft.Bcl.HashCode (>= 1.1.1)
- System.Memory (>= 4.5.3)
- System.Runtime.CompilerServices.Unsafe (>= 4.5.2)
-
.NETStandard 2.0
- Microsoft.Bcl.HashCode (>= 1.1.1)
- System.Memory (>= 4.5.3)
- System.Runtime.CompilerServices.Unsafe (>= 4.5.2)
-
.NETStandard 2.1
- System.Runtime.CompilerServices.Unsafe (>= 4.5.2)
-
net5.0
- No dependencies.
-
net6.0
- No dependencies.
-
net7.0
- No dependencies.
-
net8.0
- No dependencies.
-
net9.0
- No dependencies.
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,167 | 11/10/2024 |
2022.2.7 | 104,017 | 5/1/2022 |
2022.2.5 | 7,778 | 5/1/2022 |
2022.1.4 | 7,726 | 4/6/2022 |
2021.2.21 | 6,727 | 1/21/2022 |
2021.2.20 | 6,563 | 1/17/2022 |
2021.1.5 | 16,119 | 7/6/2021 |
2020.2.2-beta | 2,516 | 11/21/2020 |
2020.1.15 | 7,615 | 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,584 | 11/4/2019 |
2019.2.20 | 3,967 | 8/13/2019 |
2019.1.151 | 29,702 | 3/30/2019 |