Unfucked
v0.0.1-beta.56Pre-release
net10.0.NETFramework4.7net6.0net8.0net9.0.NETStandard2.0
Fix egregiously broken or missing functionality in .NET libraries. Provide useful boilerplate, façades, and polyfills.
Published
Oct 18, 2026
yesterday
Total Downloads
24.4K
24,357
Install
dotnet add package Unfucked --version 0.0.1-beta.56
| Product | Versions |
|---|---|
| .NET | |
| .NET Standard | |
| Other |
README
🧰 Unfucked
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.
Installation
dotnet package add 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 the BCL
Console.Write.string colored = Console.Color("Hello", Color.FromArgb(0xff, 0xaa, 0), Color.Black); Console.WriteLine("Hello", Color.FromArgb(0xff, 0xaa, 0), Color.Black); Console.Write("Hello", Color.FromArgb(0xff, 0xaa, 0), Color.Black); - Clear screen and move cursor.
Console.WriteClearAll(); // clear screen and move to top-left corner Console.WriteClearLine(); // clear line and move to left side - Enable colored output on Windows 10 1511 and later.
- Called implicitly whenever you call the
Console.Color,Write,WriteLine,WriteClearAll, orWriteClearLinemethods. - Can be manually checked and opportunistically enabled.
bool colorable = Console.EnableColorSupport();
- Called implicitly whenever you call the
- Draw a textual progress bar in the console, including Windows Terminal support.
double progress = 0.5; Console.OutputEncoding = Encoding.UTF8; Console.CursorVisible = false; ConsoleExtensions.ProgressBarOptions opts = new() { TotalWidth = _ => Console.WindowWidth - 5 }; Console.Write($"{Console.ClearLine}{Console.RenderProgressBar(progress, opts)} {progress,4:P0}"); // ━━━━━━━━──────── 50%
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");
Dataflow
- Simple aggregating Dataflow block that consumes all the items it's provided with until its linked source completes, then emits all consumed items in one big list (instead of one at a time like
BufferBlock). Useful for joining/aggregating/reducing the stream of items so they can all be analyzed together, like finding the minimum item. Like aBatchBlockof unbounded size, in case you don't know how many producers there are, or to insulate your code from that changes to that quantity.JoinAllBlock<int> joinAllBlock = new(); joinAllBlock.Post(1); joinAllBlock.Post(2); joinAllBlock.Post(3); joinAllBlock.Complete(); IReadOnlyCollection<int> joined = joinAllBlock.Receive(); // [1, 2, 3] - Easily create a block that produces and consumes items with custom logic, without all the boilerplate of
IPropagatorBlockor the lack of reusability ofDataflowBlock.Encapsulate. Just subclass and provide implementations of theInputandOutputblocks. Useful when the built-in blocks almost do what you want, but not quite.public class JoinAllBlock<T>: InputOutputBlock<T, IReadOnlyCollection<T>> { protected sealed override ITargetBlock<T> Input { get; } protected override IReceivableSourceBlock<IReadOnlyCollection<T>> Output { get; } private readonly IList<T> items = []; public JoinAllBlock() { Input = new ActionBlock<T>(items.Add); BufferBlock<IReadOnlyCollection<T>> outputBuffer = new(); Output = outputBuffer; Input.Completion.ContinueWith(_ => { if (items.Count != 0) { outputBuffer.Post(items.AsReadOnlyList()); } Output.Complete(); }); } } - Fix the completion propagation defect where multiple source blocks link to the same target block, then one of the sources completes, causing the target to inadvertently complete early, instead of waiting for all the other sources to also complete. Like
Task.WhenAllinstead ofTask.WhenAny. Useful when a block has multiple sources.ISourceBlock<int> source1, source2; ITargetBlock<int> target; PatientDataflowLinkOptions opts = new { PropagateCompletion = true, PropagatingCompletionWaitsForAllSources = true }; source1.LinkTo(target, opts); source2.LinkTo(target, opts); // target will complete when both source1 and source2 have completed, not just when one source has completed
Date and Time
- Absolute value of
TimeSpanwith a more discoverable name thatDuration, whose name doesn't imply absolute value at allTimeSpan absValue = TimeSpan.FromHours(-1).Abs == TimeSpan.FromHours(1);
Decimal math
Mathoperations on 128-bitdecimalvaluesATanAcosAsinAtan2CalculateSinFromCosCosCoshExpIsIntegerIsSignOfSinePositiveLogLog10PowerPowerNSinSinhSqrtTanTanhTruncateToPeriodicInterval
Directories
- Delete directory without throwing an exception on missing directories.
bool found = Directory.TryDelete(directory: "myDir", recursive: false); - Create new empty temporary subdirectory in specific parent directory
Directory.CreateTempDir(); // example: %LOCALAPPDATA%\Temp\temp-12345678 Directory.CreateTempDir(Environment.ExpandEnvironmentVariables(@"%temp%\myapp")); // example: %LOCALAPPDATA%\Temp\myapp\temp-abcdefgh
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
nullvalues.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 // if you have a value you want to be in scope in the valueFactory, you can pass it as a parameter to GetOrAdd if you want to avoid allocating a closure- 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
IAsyncEnumeratorto list.IAsyncEnumerator<string> enumerator = MyEnumerateAsync(); IReadOnlyList<string> enumerated = await enumerator.ToList(); - Get the first, last, singleton, or indexed item, or return
nullinstead ofdefaultfor 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
nullinstead ofdefault, 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, becauseTryGetValuedid 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.
- If the item type is a value-type struct, use
- 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
ConcurrentDictionaryto upsert new value and get old valueConcurrentDictionary<string, ValueHolder<int>> map = ConcurrentDictionary<string, int>.CreateWithMutableAtomicValues(Singleton.Dictionary("a", 1)); int? oldValue = map.AtomicSwap(key: "a", newValue: 2); // oldValue == 1, map["a"] == 2 - Atomic compare and swap on
ConcurrentDictionaryto update new value if it exists and matches old value, and also get the old valueConcurrentDictionary<string, ValueHolder<int>> map = ConcurrentDictionary<string, int>.CreateWithMutableAtomicValues(Singleton.Dictionary("a", 1)); int? oldValue = map.AtomicCompareAndSwap(key: "a", oldValue: 1, newValue: 2); - Atomic math on
ConcurrentDictionaryto update new value based on its old value and return the new valueConcurrentDictionary<string, ValueHolder<int>> map = ConcurrentDictionary<string, int>.CreateWithMutableAtomicValues(("a", 1).KeyValues()); int? oldValue = map.AtomicIncrement("a"); oldValue = map.AtomicDecrement("a"); oldValue = map.AtomicAdd("a", 2); - Get or add to
ConcurrentDictionaryand determine whether a new value was added or an existing value was returnedConcurrentDictionary<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); // if you have a value you want to be in scope in the valueFactory, you can pass it as a parameter to GetOrAdd if you want to avoid allocating a closure - Get or add to
ConcurrentDictionaryand 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
IDictionary<string, int> dict = IDictionary.Singleton(key: "a", value: 1); IReadOnlyDictionary<string, int> rodict = IReadOnlyDictionary<string, int>.Singleton(key: "a", value: 1); IReadOnlySet<string> set = ISet<string>.Singleton(item: "a"); IEnumerable<KeyValuePair<string, int>> kvs = (key: "a", value: 1).KeyValues(); - 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,IDictionary<K,V>.AsReadOnly, andISet<T>.AsReadOnlyfor old .NET versionsIReadOnlyList<string> roList = new List<string> { "a", "b" }.AsReadOnlyList(); IReadOnlyDictionary<int, int> roDict = new Dictionary<int, int> { {1, 2} }.AsReadOnlyDictionary(); IReadOnlySet<string> roSet = new HashSet<string> { "a" }.AsReadOnlySet(); // IReadOnlySet<T> is only public in .NET ≥ 5, so this method isn't available in older TFMs
Events
- Subscribe to a producer's events without the producer holding a strong reference to the subscriber, which would have prevented the subscriber from being garbage collected. Without this, you would need to manually unsubscribe, and convert any lambda callbacks into methods or local functions.
publisher.EventHappened += WeakSubscriber.Subscribe<EventHandler>((object? sender, EventArgs args) => Console.WriteLine("Event happened")); - Allow event subscribers to be asynchronous, and publishers can await running their callbacks.
public event AsyncEventHandler<string>? helloSent; helloSent += async (object? sender, string message) => { await Task.Delay(100); Console.WriteLine($"Hello {message}!"); }; await helloSent?.Invoke(this, "world");
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.CauseChain; // 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
IOExceptionwas 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
falsethere.
- This case is undetectable on Linux and Mac OS, so it always returns
Files
- Delete a file, even if it is read-only.
Files.Delete("filename");
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
Path.TrimTrailingSlashes(@"C:\Users\Ben\Desktop\") == @"C:\Users\Ben\Desktop" Path.TrimTrailingSlashes("/home/ben/") == "/home/ben" - Convert DOS backslashes to Unix forward slashes
Path.Dos2UnixSlashes(@"C:\Users\Ben\Desktop") == "C:/Users/Ben/Desktop" - Match file extension against a set
FrozenSet<string> videoExtensions { get; } = FrozenSet.Create( ".3gpp", ".asf", ".avi", ".bik", ".divx", ".dv", ".f4v", ".flv", ".m1v", ".m4v", ".mkv", ".mov", ".mp4", ".mp4v", ".mpeg", ".mpg", ".ts", ".vob", ".webm", ".wmv"); bool hasVideoExt = Path.MatchesExtensions("myfile.mp4", videoExtensions);
Processes
- Command line argument marshalling with correct escaping and quoting
- String → array (requires
Unfucked.Windowspackage)IEnumerable<string> argv = WindowsProcesses.CommandLineToEnumerable("arg1 arg2"); - Array → string
string args = Process.CommandLineToString(["arg1", "'argument' \"2\""]);
- String → array (requires
- Run program and get output and exit code, like Node.js'
child_process.execFile()(int exitCode, string stdout, string stderr) = await Process.ExecFile("path/to/program.exe", ["arg1", "arg2"], "workDir", extraEnv, hideWindow: false, ct); - Easily throw an exception if the program was could not start or exited with a non-zero status code.
ProcessResult result = (await Process.ExecFile("program.exe")) .EnsureSuccessful<ApplicationException>("Process failed", ProcessResult.Details.All); - Determine whether the current program is a console or Windows GUI app.
bool isWindowsGuiProgram = Process.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
andnew[] { "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" - Truncate the beginning or end of a string, never leaving widowed or orphaned Unicode surrogate code points.
"Hello 👖".Truncate(7); // "Hello " "👖 Hello".Truncate(-7); // " Hello" - Typesafe
SearchValues.Createthat prevents callers from passing illegalStringComparisonvalues at compile time, because the BCL method allows Current Culture and Invariant comparisons at compile time only to crash at runtime.SearchValues<string> searchValues = SearchValues.Create(["a", "b"], caseSensitive: true); - Polyfills for
StringBuilder.AppendJoinin .NET Standard 2.0 - Polyfills for
string.StartsWith(char)andstring.EndsWith(char)in .NET Standard 2.0 - Polyfills for
string.Contains(string, StringComparison)in .NET Standard 2.0 - Polyfills for
string.Joinoverloads that takeReadOnlySpanin .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 Task.LongDelay(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
trueif any passed orfalseif they all failedTask<string> a, b; bool any = await Task.WhenAny([a, b], s => s.Length > 1); - Return the first passing task's result, or
nullif they all failedTask<string> a, b; string? firstOrDefault = await Task.FirstOrDefault([a, b], s => s.Length > 1);
- Return
- Asynchronously await the cancellation of a
CancellationTokenwithout blocking the thread, which is especially important to prevent a deadlock if a CancellationToken is used to keep your main thread from exitingawait cancellationToken.Wait(); - Cancel a
CancellationTokenwhen the user presses Ctrl+CCancellationTokenSource cts = new(); cts.CancelOnCtrlC(); - Get the result of a task, or
nullif it threw an exception, to allow fluent null-coalescing to a fallback chain, instead of a temporary variable and multi-linetry/catchblock statementobject resultWithFallback = await Task.FromException<object>(new Exception()).ExceptionToNull() ?? new object(); - Easily await tasks that may be null without having to add parentheses and null coalescing operators.
Task? myOptionalTask = null; await myOptionalTask.OrComplete();
URIs
- Fluent property to get URL query parameters
string? value = new Uri("https://aldaviva.com?key=value").QueryParams["key"]; - List individual path segments without annoying slash separators or URL encoding
IReadOnlyList<string> pathSegments = new Uri("https://aldaviva.com/a/b%2Fb/c.html").Path; // returns ["a", "b/b", "c.html"] // BCL Uri.Segments returns ["/", "a/", "b%2Fb/", "c.html"], and its unintuitive name makes it hard to discover - 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(UriExtensions.Part.Query); // https://[email protected]:443/path?key=value full.Truncate(UriExtensions.Part.Path); // https://[email protected]:443/path full.Truncate(UriExtensions.Part.Authority); // https://[email protected]:443/ full.Truncate(UriExtensions.Part.Origin); // https://aldaviva.com:443
Versions
- Format version numbers without crashing if the input had too few components, and trim trailing zero components
Version.Parse("1.0").ToString(4, 4) → "1.0.0.0" // does not throw an exception Version.Parse("1.0.0.0").ToString(1, 4) → "1" // trims trailing zeros to have [1,4] components Version.Parse("1.2.3.4").ToString(1, 4) → "1.2.3.4" // trims trailing zeros to have [1,4] components - Get the program's version number, or print it to stdout and exit with status code 0.
Console.WriteLine(Version.ProgramVersion); // prints output like the following to stdout and does not exit5.0.1// useful at the beginning of Main Version.PrintProgramVersionAndExitIfRequested(); // prints output like the following to stdout and exits with return code 0 if started with arguments -v, --version, or -versionVersion: 5.0.1 Commit: e7a9d7252f1e8d0cdae7d47358039192fa5a4af4 Built: Monday, May 18, 2026 3:31:15 p (-07:00)
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
No structural changes between v0.0.1-beta.55 and v0.0.1-beta.56.
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 6 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.
