Skip to main content
net6.0net8.0net9.0.NETStandard2.0

Fix egregiously broken or missing functionality in .NET libraries. Provide useful boilerplate, faƧades, and polyfills.

Published
Nov 9, 2025
11mo ago
Total Downloads
24.4K
24,357

Install

dotnet add package Unfucked --version 0.0.1-beta.23
ProductVersions
.NET
net6.0net8.0net9.0
.NET Standard
netstandard2.0

README

🧰 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);
    

    [!WARNING] 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 Ctrl+C
    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();
    

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://[email protected]:443/path?key=value#hash");
    full.Truncate(URI.Part.Query);     // https://[email protected]:443/path?key=value
    full.Truncate(URI.Part.Path);      // https://[email protected]:443/path
    full.Truncate(URI.Part.Authority); // https://[email protected]: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

Changes from Previous Version

v0.0.1-beta.22v0.0.1-beta.23
Added Frameworks
net9.0

Adoption Assessment

Assess this package against its supported frameworks, license, maintenance signals and dependency graph before making it a project standard.

This version is marked prerelease. Validate its API and upgrade path before production use.Compatibility metadata lists 4 target frameworks; confirm the exact target used by your application.

Related Guides & Workflows

Learn how to evaluate, integrate, or audit this package in your .NET project.