Skip to main content
Unfucked package icon

Unfucked

v0.0.1-beta.57Latest Pre-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.57
ProductVersions
.NET
net10.0net6.0net8.0net9.0
.NET Standard
netstandard2.0
Other
.NETFramework4.7

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 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, or WriteClearLine methods.
    • Can be manually checked and opportunistically enabled.
      bool colorable = Console.EnableColorSupport();
      
  • 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 a BatchBlock of 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 IPropagatorBlock or the lack of reusability of DataflowBlock.Encapsulate. Just subclass and provide implementations of the Input and Output blocks. 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.WhenAll instead of Task.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 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 = 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 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
    // 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 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 = 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 ConcurrentDictionary to update new value if it exists and matches old value, and also get the old value
    ConcurrentDictionary<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 ConcurrentDictionary to update new value based on its old value and return the new value
    ConcurrentDictionary<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 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);
    // 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 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
    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, and ISet<T>.AsReadOnly for old .NET versions
    IReadOnlyList<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 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.

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.Windows package)
      IEnumerable<string> argv = WindowsProcesses.CommandLineToEnumerable("arg1 arg2");
      
    • Array → string
      string args = Process.CommandLineToString(["arg1", "'argument' \"2\""]);
      
  • 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 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"
    
  • 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.Create that prevents callers from passing illegal StringComparison values 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.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 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 true if any passed or false if they all failed
      Task<string> a, b;
      bool any = await Task.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 Task.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()).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 exit
    
    5.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 -version
    
    Version: 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.56 and v0.0.1-beta.57.

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.