Unfucked 0.0.1-beta.31

This is a prerelease version of Unfucked.
dotnet add package Unfucked --version 0.0.1-beta.31
                    
NuGet\Install-Package Unfucked -Version 0.0.1-beta.31
                    
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="Unfucked" Version="0.0.1-beta.31" />
                    
For projects that support PackageReference, copy this XML node into the project file to reference the package.
<PackageVersion Include="Unfucked" Version="0.0.1-beta.31" />
                    
Directory.Packages.props
<PackageReference Include="Unfucked" />
                    
Project file
For projects that support Central Package Management (CPM), copy this XML node into the solution Directory.Packages.props file to version the package.
paket add Unfucked --version 0.0.1-beta.31
                    
#r "nuget: Unfucked, 0.0.1-beta.31"
                    
#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.
#:package Unfucked@0.0.1-beta.31
                    
#:package directive can be used in C# file-based apps starting in .NET 10 preview 4. Copy this into a .cs file before any lines of code to reference the package.
#addin nuget:?package=Unfucked&version=0.0.1-beta.31&prerelease
                    
Install as a Cake Addin
#tool nuget:?package=Unfucked&version=0.0.1-beta.31&prerelease
                    
Install as a Cake Tool

🧰 Unfucked

NuGet GitHub Actions Testspace Coveralls

Fix egregiously broken or missing functionality in .NET libraries. Provide useful boilerplate, façades, and polyfills. Inspired by underscore, lodash, Apache Commons, Spring, Guava, jQuery,Dojo, mootools, and Prototype.

Unfuck your house

Installation

dotnet add package Unfucked
using Unfucked;

Usage

Comparables

  • Clip a value to a specified range (also known as clamping, limiting, and truncating).
    int health = health.Clip(min: 0, max: 100);
    

Console

  • Colored text and background in strings, not tightly coupled to Console.Write.
    string colored = ConsoleControl.Color("Hello", Color.FromArgb(0xff, 0xaa, 0), Color.Black);
    ConsoleControl.WriteLine("Hello", Color.FromArgb(0xff, 0xaa, 0), Color.Black);
    ConsoleControl.Write("Hello", Color.FromArgb(0xff, 0xaa, 0), Color.Black);
    
  • Clear screen and move cursor.
    ConsoleControl.Clear();     // clear screen and move to top-left corner
    ConsoleControl.ClearLine(); // clear line and move to left side
    
  • Enable colored output on Windows 10 1511 and later.
    • Called implicitly whenever you call the ConsoleControl.Color, Write, WriteLine, Clear, or ClearLine methods.
    • Can be manually checked and opportunistically enabled.
      bool colorable = ConsoleControl.IsColorSupported();
      

Cryptography

  • Random string generation
    string randomString = Cryptography.GenerateRandomString(length: 20);
    
  • Is certificate temporally valid?
    X509Certificate2 myCert;
    bool isValid = myCert.IsTemporallyValid(safetyMargin: TimeSpan.Zero, now: DateTime.Now);
    

    Only checks time validity, not trust chain, revocation, or algorithm weakness.

  • Get certificate subject/issuer named part.
    X509Certificate2 myCert;
    string? subjectCommonName = myCert.SubjectName.Get("CN");
    string? issuerOrg = myCert.Issuer.Get("O");
    

Date and Time

  • Absolute value of TimeSpan with a more discoverable name that Duration, whose name doesn't imply absolute value at all
    TimeSpan absValue = TimeSpan.FromHours(-1).Abs() == TimeSpan.FromHours(1);
    

Decimal math

  • Math operations on 128-bit decimal values
    • ATan
    • Acos
    • Asin
    • Atan2
    • CalculateSinFromCos
    • Cos
    • Cosh
    • Exp
    • IsInteger
    • IsSignOfSinePositive
    • Log
    • Log10
    • Power
    • PowerN
    • Sin
    • Sinh
    • Sqrt
    • Tan
    • Tanh
    • TruncateToPeriodicInterval

Directories

  • Delete directory without throwing an exception on missing directories.
    bool found = Directories.TryDelete(directory: "myDir", recursive: false);
    

DNS

  • Fluent resolving method
    IPEndPoint? ipAddress = await new DnsEndPoint("aldaviva.com", 443).Resolve();
    
    • If you need to resolve anything other than an A or AAAA record, take a look at DnsClient.NET instead

Enumerables

  • Filter out null values.
    IEnumerable<string?> sparseList = new List<string?> { "a", "b", null };
    IEnumerable<string> list = withNulls.Compact(); // ["a", "b"]
    
    IDictionary<string, object?> sparseMap = new Dictionary<string, object?> {
        ["a"] = "AA",
        ["b"] = "BB",
        ["c"] = null
    };
    IDictionary<string, object> map = sparseMap.Compact(); // { "a": "AA", "b": "BB" }
    
  • Add multiple values at once.
    IList<string> list = ["a"];
    list.AddAll("b", "c"); // ["a", "b", "c"]
    
  • Upsert into non-concurrent dictionary.
    Dictionary<string, string> map = new() {
        ["a"] = "AA"
    };
    
    string a = map.GetOrAdd(key: "a", value: "AA", out bool aAdded); // a == "AA", aAdded == false
    string b = map.GetOrAdd(key: "b", valueFactory: () => "BB", out bool bAdded); // b == "BB", bAdded == true
    
    • Value factories can also be asynchronous
  • Fluent set difference method
    ISet<string> original = new HashSet<string> { "a", "b", "c" };
    ISet<string> subtract = new HashSet<string> { "a", "b" };
    ISet<string> diff = original.Minus(subtract); // ["CC"]
    
  • Filter out consecutive duplicate values.
    IEnumerable<string> original = ["a", "a", "b", "b", "c", "b"];
    IEnumerable<string> deduped = original.DistinctConsecutive(); // ["a", "b", "c", "b"]
    
  • Convert IAsyncEnumerator to list.
    IAsyncEnumerator<string> enumerator = MyEnumerateAsync();
    IReadOnlyList<string> enumerated = await enumerator.ToList();
    
  • Get the first, last, singleton, or indexed item, or return null instead of default for value types, which are ambiguous with present elements.
    IEnumerable<TimeSpan> ts = [TimeSpan.FromSeconds(1), TimeSpan.FromSeconds(2)];
    TimeSpan? head     = ts.FirstOrNull();                                          // 1 second
    TimeSpan? last     = ts.LastOrNull();                                           // 2 seconds
    TimeSpan singleton = ts.SingleOrNull() ?? TimeSpan.FromSeconds(3);              // 3 seconds
    TimeSpan t         = ts.ElementAtOrNull(index: 100) ?? TimeSpan.FromSeconds(4); // 4 seconds
    
  • Get an element from a dictionary by key, or return null instead of default, because default value types are ambiguous with present elements. This avoids having to use an awkward, verbose TryGetValue in a ternary operator every time. Also includes methods for reference typed keys, because TryGetValue did not exist in .NET Standard 2.0.
    var structDict = new Dictionary<string, int> { { "a", 0 } };
    int? a = structDict.GetValueOrNullStruct("a"); // 0
    int? b = structDict.GetValueOrNullStruct("b"); // null instead of 0, the default value of int, which is ambiguous with the presence of "b"
    
    var classDict = new Dictionary<string, string> { { "c", "0" } };
    string? c = classDict.GetValueOrNull("c"); // "0"
    string? d = classDict.GetValueOrNull("d"); // null
    
  • Head and tail
    IEnumerable<string> original = ["a", "b", "c"];
    (string? head, IEnumerable<string> tail) = original.HeadAndTail();
    // head == "a", tail == ["b", "c"]
    
    • If the item type is a value-type struct, use HeadAndTailStruct() instead.
  • Delta changeset between two enumerables (created, updated, deleted, and unchanged items)
    public record Person(long id, string name);
    IEnumerable<string> original = [new Person(1, "Alice"), new Person(2, "Bob"), new Person(3, "Charlie")];
    IEnumerable<string> @new = [new Person(1, "Alice"), new Person(3, "Carol"), new Person(4, "Dan")];
    
    (IEnumerable<Person> created, IEnumerable<Person> updated, IEnumerable<Person> deleted, IEnumerable<Person> unmodified) 
        = original.DeltaWith(@new, person => person.id);
    // created contains Dan
    // updated contains Carol
    // deleted contains Bob
    // unmodified contains Alice
    
    • The original and new items don't have to be of the same type.
  • Atomic swap on ConcurrentDictionary to upsert new value and get old value
    ConcurrentDictionary<string, ValueHolder<int>> map = Enumerables.CreateConcurrentDictionary(Singleton.Dictionary("a", 1));
    int? oldValue = map.Swap(key: "a", newValue: 2); // oldValue == 1, map["a"] == 2
    
  • Atomic compare and swap on ConcurrentDictionary to update new value if it exists and matches old value, and also get the old value
    ConcurrentDictionary<string, ValueHolder<int>> map = Enumerables.CreateConcurrentDictionary(Singleton.Dictionary("a", 1));
    int? oldValue = map.CompareAndSwap(key: "a", oldValue: 1, newValue: 2);
    
  • Get or add to ConcurrentDictionary and determine whether a new value was added or an existing value was returned
    ConcurrentDictionary<long, Person> clients = new();
    Person alice = clients.GetOrAdd(key: 1, newValue: new Person("Alice"), out bool aliceAdded);
    Person bob   = clients.GetOrAdd(key: 2, valueFactory: id => new Person(id, "Bob"), out bool bobAdded);
    
  • Get or add to ConcurrentDictionary and dispose of created but unadded values.
    ConcurrentDictionary<string, HttpClient> clients = new();
    HttpClient actual = clients.GetOrAddWithDisposal(key: "aldaviva.com", valueFactory: domain => new HttpClient { BaseAddress = domain }, out bool added);
    // if clients already contained the key "aldaviva.com", then
    //     actual will be the existing client,
    //     added will be false, and
    //     any new HttpClient that may have been temporarily created for insertion before being discarded will be disposed
    // otherwise, actual will be a new HttpClient instance, and added will be true
    
  • Factories for singleton Dictionaries, Sets, and enumerables of key-value pairs
    IReadOnlyDictionary<string, int>       dict = Singleton.Dictionary(key: "a", value: 1);
    IReadOnlySet<string>                   set  = Singleton.Set(item: "a");
    IEnumerable<KeyValuePair<string, int>> kvs  = Singleton.KeyValues(key: "a", value: 1);
    
  • Filter by exact type instead of by superclass.
    public class Superclass;
    public class Subclass: Superclass;
    
    IEnumerable<object> a = [new Superclass(), new Subclass()];
    IEnumerable<Superclass> superclasses = a.OfTypeExactly<Superclass>();
    // superclasses does not contain any Subclass instances
    
  • Polyfill for IList<T>.AsReadOnly for .NET versions before 8, including .NET Standard
    IReadOnlyList<string> ro = new List<string> { "a", "b" }.AsReadOnly();
    

Exceptions

  • Get the chain of causes for an exception, which is a sequence of all of its inner exceptions, recursively. Excludes outermost exception.
    Exception e;
    IEnumerable<Exception> causeChain = e.GetCauseChain();
    // equivalent to [e.InnerException, e.InnerException.InnerException, ...]
    
  • Get the chain of messages for an exception, which is a string of all of its inner messages, recursively. Includes outermost message.
    Exception e;
    string messages = e.MessageChain(includeClassNames: true);
    // like $"Exception: outer message; OtherExceptionClass: inner message; ..."
    
  • Determine if an IOException was caused by a file already existing on Windows.
    try {
        Stream file = new FileStream("filename", FileMode.CreateNew, FileAccess.Write);
    } catch (IOException e) when (e.IsCausedByExistingWindowsFile()){
        // filename already exists
    }
    
    • This case is undetectable on Linux and Mac OS, so it always returns false there.

Lazy

  • Easily dispose of lazy value without all the conditional and exception handling boilerplate
    Lazy<HttpClient> httpClientHolder = new(() => new HttpClient(), LazyThreadSafetyMode.PublicationOnly);
    httpClientHolder.TryDisposeValue();
    

Paths

  • Trim trailing slashes
    Paths.TrimTrailingSlashes(@"C:\Users\Ben\Desktop\") == @"C:\Users\Ben\Desktop"
    Paths.TrimTrailingSlashes("/home/ben/") == "/home/ben"
    
  • Create new empty temporary subdirectory in specific parent directory
    Paths.CreateTempDir(); // example: %LOCALAPPDATA%\Temp\temp-12345678
    Paths.CreateTempDir(Environment.ExpandEnvironmentVariables(@"%temp%\myapp")); // example: %LOCALAPPDATA%\Temp\myapp\temp-abcdefgh
    
  • Convert DOS backslashes to Unix forward slashes
    Paths.Dos2UnixSlashes(@"C:\Users\Ben\Desktop") == "C:/Users/Ben/Desktop"
    
  • Match file extension against a set
    IReadOnlySet<string> videoExtensions { get; } = new HashSet<string> {
           ".3gpp", ".asf", ".avi", ".bik", ".divx", ".dv", ".f4v", ".flv", ".m1v", ".m4v", ".mkv", ".mov", ".mp4", ".mp4v", ".mpeg", ".mpg", ".ts", ".vob", ".webm", ".wmv"}.ToFrozenSet();
    bool hasVideoExt = Paths.MatchesExtensions("myfile.mp4", videoExtensions);
    

Processes

  • Command line argument marshalling with correct escaping and quoting
    • String to array (requires Unfucked.Windows package)
      IEnumerable<string> argv = WindowsProcesses.CommandLineToEnumerable("arg1 arg2");
      
    • Array to string
      string args = Processes.CommandLineToString(["arg1", "'argument' \"2\""]);
      
  • Run program and get output and exit code, like Node.js' child_process.execFile()
    (int exitCode, string stdout, string stderr) result = 
        await Processes.ExecFile("path/to/program.exe", ["arg1", "arg2"], extraEnv, "workDir", hideWindow: false, ct);
    
  • Determine whether the current program is a console or Windows GUI app.
    bool isWindowsGuiProgram = Processes.IsWindowsGuiProgram();
    

Strings

  • Coerce empty strings to null
    "".EmptyToNull(); // null
    
  • Fluently check if a string has any non-whitespace characters
    " ".HasText(); // false
    
  • Fluently check if a string has any characters
    "".HasLength(); // false
    
  • Fluent join method
    new[] { "A", "B" }.Join(", ");
    
  • Uppercase first letter
    "ben".ToUpperFirstLetter(); // "Ben"
    
  • Lowercase first letter
    "Ben".ToLowerFirstLetter(); // "ben"
    
  • Trim multiple strings from start, end, or both
    "..::Ben::..".Trim(".", ":"); // "Ben"
    
  • Join enumerable into an English-style list with commas an a conjunction like and
    new[] { "Dewey", "Cheetum", "Howe" }.JoinHumanized(",", "and", true); // "Dewey, Cheetum, and Howe"
    
  • Fluent conversion method to byte array
    "Ben".ToByteArray(new UTF8Encoding(false, true)); // [0x42, 0x65, 0x6E]
    
  • Fluent conversion method to byte stream
    Stream stream = "Ben".ToByteStream();
    
  • Convert DOS CRLF line breaks to Unix LF line breaks
    "A\r\nB".Dos2Unix(); // "A\nB"        
    
  • Repeat a string a certain number of times
    "Ben".Repeat(4); // "BenBenBenBen"
    
  • Polyfills for StringBuilder.AppendJoin in .NET Standard 2.0
  • Polyfills for string.StartsWith(char) and string.EndsWith(char) in .NET Standard 2.0
  • Polyfills for string.Contains(string, StringComparison) in .NET Standard 2.0
  • Polyfills for string.Join overloads that take ReadOnlySpan in .NET Standard 2.0 and .NET Runtimes < 9

Tasks

  • Unbounded delay time (.NET ≥ 6 tops out at 49.7 days, .NET < 6 tops out at 24.9 days)
    await Tasks.Delay(TimeSpan.FromDays(365));
    
  • Await multiple tasks and proceed when any of them both completes and the return value passes a predicate, or they all fail to complete or the predicate
    • Return true if any passed or false if they all failed
      Task<string> a, b;
      bool any = await Tasks.WhenAny([a, b], s => s.Length > 1);
      
    • Return the first passing task's result, or null if they all failed
      Task<string> a, b;
      string? firstOrDefault = await Tasks.FirstOrDefault([a, b], s => s.Length > 1);
      
  • Asynchronously await the cancellation of a CancellationToken without blocking the thread, which is especially important to prevent a deadlock if a CancellationToken is used to keep your main thread from exiting
    await cancellationToken.Wait();
    
  • Cancel a CancellationToken when the user presses <kbd>Ctrl</kbd>+<kbd>C</kbd>
    CancellationTokenSource cts = new();
    cts.CancelOnCtrlC();
    
  • Get the result of a task, or null if it threw an exception, to allow fluent null-coalescing to a fallback chain, instead of a temporary variable and multi-line try/catch block statement
    object resultWithFallback = await Task.FromException<object>(new Exception()).ResultOrNullForException() ?? new object();
    
  • Easily await tasks that may be null without having to add parentheses and null coalescing operators.
    Task? myOptionalTask = null;
    await myOptionalTask.CompleteIfNull();
    

URIs

  • Fluent method to get URL query parameters
    string? value = new Uri("https://aldaviva.com?key=value").GetQuery()["key"];
    
  • Builder pattern for URLs
    Uri url = new UrlBuilder("https", "aldaviva.com")
        .Path("a")
        .Path("b/c")
        .QueryParam("d", "e")
        .QueryParam("f", "{f}")
        .ResolveTemplate("f", "g")
        .Fragment("h")
        .ToUrl(); // https://aldaviva.com/a/b/c?d=e&f=g#h
    
  • Truncate URIs to remove the fragment, query parameters, or path. Useful for getting the origin too.
    Uri full = new("https://ben@aldaviva.com:443/path?key=value#hash");
    full.Truncate(URI.Part.Query);     // https://ben@aldaviva.com:443/path?key=value
    full.Truncate(URI.Part.Path);      // https://ben@aldaviva.com:443/path
    full.Truncate(URI.Part.Authority); // https://ben@aldaviva.com:443/
    full.Truncate(URI.Part.Origin);    // https://aldaviva.com:443
    

XML

  • Fluent methods to read an XML document from an HTTP response body as a mapped object, DOM, LINQ, or XPath
    using HttpResponseMessage response = await new HttpClient().GetAsync(url);
    MyClass        obj   = await response.Content.ReadObjectFromXmlAsync<MyClass>();
    XmlDocument    dom   = await response.Content.ReadDomFromXmlAsync();
    XDocument      linq  = await response.Content.ReadLinqFromXmlAsync();
    XPathNavigator xpath = await response.Content.ReadXPathFromXmlAsync();
    
  • Find all descendant elements of a parent node which have a given tag name.
    XDocument doc = XDocument.Parse(xmlString);
    IEnumerable<XElement> els = doc.Descendants("head", "body");
    

All Unfucked libraries

Product Compatible and additional computed target framework versions.
.NET net5.0 was computed.  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 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 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.  net9.0-android was computed.  net9.0-browser was computed.  net9.0-ios was computed.  net9.0-maccatalyst was computed.  net9.0-macos was computed.  net9.0-tvos was computed.  net9.0-windows was computed.  net10.0 was computed.  net10.0-android was computed.  net10.0-browser was computed.  net10.0-ios was computed.  net10.0-maccatalyst was computed.  net10.0-macos was computed.  net10.0-tvos was computed.  net10.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 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 (6)

Showing the top 5 NuGet packages that depend on Unfucked:

Package Downloads
Unfucked.HTTP

Fix egregiously broken or missing functionality in System.Net.Http.HttpClient.

SolCalc

Find when sunrise, sunset, and different twilights happen for a given location, based on the NOAA ESRL Solar Calculator. Features high accuracy across several millenia, atmospheric refraction, a simple enumeration-based API, and multiple/missing events during polar night/day/twilight at extreme latitudes.

Unfucked.DI

Fix egregiously broken or missing functionality in Microsoft.Extensions.Hosting dependency injection.

Unfucked.Windows

Fix egregiously broken or missing functionality in .NET libraries that integrate with Windows APIs.

Unfucked.STUN

Fix egregiously broken or missing functionality in Stun.Net.

GitHub repositories

This package is not used by any popular GitHub repositories.

Version Downloads Last Updated
0.0.1-beta.31 300 12/7/2025
0.0.1-beta.30 172 11/25/2025
0.0.1-beta.29 428 11/20/2025
0.0.1-beta.28 363 11/20/2025
0.0.1-beta.27 361 11/19/2025
0.0.1-beta.26 352 11/17/2025
0.0.1-beta.25 2,481 11/11/2025
0.0.1-beta.24 176 11/9/2025
0.0.1-beta.23 118 11/9/2025
0.0.1-beta.22 90 11/8/2025
0.0.1-beta.21 90 11/8/2025
0.0.1-beta.20 231 11/6/2025
0.0.1-beta.19 145 11/2/2025
0.0.1-beta.18 168 10/28/2025
0.0.1-beta.17 160 10/27/2025
0.0.1-beta.16 163 10/22/2025
0.0.1-beta.15 176 10/20/2025
0.0.1-beta.14 180 10/19/2025
0.0.1-beta.13 331 9/25/2025
0.0.1-beta.12 634 7/23/2025
0.0.1-beta.11 549 7/23/2025
0.0.1-beta.10 197 7/13/2025
0.0.1-beta.9 262 7/3/2025
0.0.1-beta.8 172 6/29/2025
0.0.1-beta.7 169 6/29/2025
0.0.1-beta.6 175 6/26/2025
0.0.1-beta.5 306 6/5/2025
0.0.1-beta.4 146 6/5/2025
0.0.1-beta.3 215 5/21/2025
0.0.1-beta.2 183 5/9/2025
0.0.1-beta.1 255 4/12/2025
0.0.0-beta9 206 4/7/2025
0.0.0-beta8 508 3/11/2025
0.0.0-beta7 251 3/8/2025
0.0.0-beta6 329 3/6/2025
0.0.0-beta5 253 3/6/2025
0.0.0-beta4 326 1/20/2025
0.0.0-beta3 246 9/30/2024
0.0.0-beta2 184 9/8/2024
0.0.0-beta1 103 9/7/2024
0.0.0-alpha2 100 9/7/2024
0.0.0-alpha1 110 8/10/2024