PurelySharp.Attributes 0.0.1

There is a newer version of this package available.
See the version list below for details.
dotnet add package PurelySharp.Attributes --version 0.0.1
                    
NuGet\Install-Package PurelySharp.Attributes -Version 0.0.1
                    
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="PurelySharp.Attributes" Version="0.0.1" />
                    
For projects that support PackageReference, copy this XML node into the project file to reference the package.
<PackageVersion Include="PurelySharp.Attributes" Version="0.0.1" />
                    
Directory.Packages.props
<PackageReference Include="PurelySharp.Attributes" />
                    
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 PurelySharp.Attributes --version 0.0.1
                    
#r "nuget: PurelySharp.Attributes, 0.0.1"
                    
#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.
#addin nuget:?package=PurelySharp.Attributes&version=0.0.1
                    
Install PurelySharp.Attributes as a Cake Addin
#tool nuget:?package=PurelySharp.Attributes&version=0.0.1
                    
Install PurelySharp.Attributes as a Cake Tool

PurelySharp

A C# analyzer that enforces method purity through the [EnforcePure] or [Pure] attribute. Methods marked with this attribute must be pure (no side effects, only pure operations).

Beta Warning ⚠️

This analyzer is currently in beta. Some cases may not be properly detected or may produce false positives.

Known limitations:

  • Indirect Impurities: The analyzer may not detect impure operations across multiple method calls, especially if those methods are in different assemblies.
  • Static Fields: Access to static fields from other types/assemblies may not always be correctly identified as impure.
  • External Dependencies: Methods from third-party libraries without proper annotations might be incorrectly assumed to be pure.
  • Reflection: Code using reflection to modify state may evade detection.
  • Thread-Static Fields: Thread-static field access isn't always properly detected as impure.
  • Collection Modifications: The analyzer may not detect all collection modifications, especially through indirect method calls.
  • Delegate Invocations: Delegate invocations to impure methods might not be detected.

For best results, ensure that all called methods in the dependency chain are also marked with [EnforcePure] when appropriate.

Installation

To use PurelySharp in your project, you need to:

  1. Install the Attributes NuGet Package: The [EnforcePure] attribute is defined in a separate package. You need to add this to any project where you intend to use the attribute. Since version 0.0.1 might be unlisted, use the .NET CLI command:

    dotnet add package PurelySharp.Attributes --version 0.0.1
    

    (Or add the reference manually to your .csproj file.)

  2. Install the VSIX Extension: For the analyzer to run in Visual Studio and provide real-time feedback and code fixes, install the PurelySharp VSIX extension from the Visual Studio Marketplace.

Supported Language Features

Supported means there is some level of test coverage. It does not mean it is 100% supported.

Expressions

  • Literal expressions (numbers, strings, etc.)
  • Identifiers (variables, parameters)
  • Method invocations (if the method is pure)
  • Member access (properties, fields - if readonly)
  • Object creation (for immutable types)
  • Tuple expressions and deconstruction
  • Switch expressions (C# 8.0+)
  • Pattern matching
  • Null coalescing operators (??, ?.)
  • Interpolated strings
  • Stack allocations and Span operations (when used in a read-only manner)
  • Indices and ranges (C# 8.0+)
  • Bit shift operations including unsigned right shift (>>>)
  • Async/await expressions
  • Unsafe code blocks
  • Pointer operations

Statements

  • Local declarations
  • Return statements
  • Expression statements
  • If statements
  • Switch statements
  • Throw statements
  • Try-catch-finally blocks (if all blocks are pure)
  • Local functions (if body is pure)
  • Using statements
  • Using declarations (C# 8.0+)
  • Lock statements (when used with [AllowSynchronization] attribute and read-only lock objects)
  • Yield statements (iterator methods)
  • Fixed statements

Collections and Data Structures

  • Immutable collections (System.Collections.Immutable)
  • Read-only collections (IReadOnly* interfaces)
  • Arrays (when used in a read-only manner)
  • Tuples
  • [~] Collection expressions (C# 12) - Partial support: only when creating immutable collections
  • Mutable collections (List, Dictionary, etc.)
  • Modifying collection elements
  • Inline arrays (C# 12)

Method Types

  • Regular methods
  • Expression-bodied methods
  • Extension methods (if pure)
  • Local functions (if pure)
  • Abstract methods
  • Recursive methods (if pure)
  • Virtual/override methods (if pure)
  • Generic methods
  • Async methods
  • Iterator methods (yield return)
  • Unsafe methods
  • Operator overloads (including checked operators)
  • User-defined conversions
  • Static abstract/virtual interface members (C# 11)

Type Declarations

  • Classes (when methods are pure)
  • Interfaces (methods are considered pure by default)
  • [~] Structs (when methods are pure) - Partial support: only for immutable structs
  • Records (C# 9)
  • Record structs (C# 10)
  • Enums
  • Delegates
  • File-local types (C# 11)
  • Primary constructors (C# 12)

Member Declarations

  • Instance methods
  • Static methods
  • Constructors
  • Properties (get-only)
  • Auto-properties (get-only or init-only)
  • Fields (readonly)
  • Events
  • Indexers
  • Required members (C# 11)
  • Partial properties (C# 13)

Parameter Types

  • Value types
  • Reference types (when used in a read-only manner)
  • Ref parameters
  • Out parameters
  • In parameters
  • Params arrays
  • Params collections (C# 13)
  • Optional parameters
  • Optional parameters in lambda expressions (C# 12)
  • Ref readonly parameters (C# 12)

Special Features

  • LINQ methods (all considered pure)
  • String operations (all considered pure)
  • Math operations (all considered pure)
  • Tuple operations
  • Conversion methods (Parse, TryParse, etc.)
  • I/O operations (File, Console, etc.)
  • Network operations
  • Threading/Task operations
  • Random number generation
  • Event subscription/invocation
  • Delegate invocation

Advanced Language Features

  • Pattern matching

  • Switch expressions

  • List patterns (C# 11)

  • Top-level statements (C# 9)

  • File-scoped namespaces (C# 10)

  • Required members (C# 11)

  • Nullable reference types annotations (C# 8.0+)

  • Caller information attributes

  • Source generators

  • Partial classes/methods

  • Global using directives (C# 10)

  • Generic attributes (C# 11)

  • Type alias for any type (C# 12)

  • Experimental attribute (C# 12)

  • Interceptors (C# 12 preview)

  • Lock object (C# 13)

  • Overload resolution priority (C# 13)

C# 11 Specific Features

  • Extended nameof scope
  • Numeric IntPtr (nint/nuint)
  • Generic attributes
  • Unsigned right-shift operator (>>>)
  • Checked user-defined operators
  • Raw string literals
  • UTF-8 string literals
  • List patterns
  • File-local types
  • Required members
  • Auto-default structs
  • Pattern match Span<char> on constant string
  • Newlines in string interpolation expressions
  • ref fields and scoped ref
  • Generic math support (static virtual/abstract interface members)

C# 12 Specific Features

  • [~] Collection expressions - Partial support: only when creating immutable collections
  • Primary constructors
  • Inline arrays
  • Optional parameters in lambda expressions
  • ref readonly parameters
  • Type alias for any type
  • Experimental attribute
  • Interceptors (preview)

C# 13 Specific Features

  • params collections
  • Lock object
  • Escape sequence \e
  • Method group natural type improvements
  • Implicit indexer access in object initializers
  • ref/unsafe in iterators/async
  • ref struct interfaces
  • Overload resolution priority
  • Partial properties
  • field contextual keyword (preview)

Field/Property Access

  • Readonly fields
  • Const fields
  • Get-only properties
  • Init-only properties (C# 9)
  • Mutable fields
  • Properties with setters
  • Static mutable fields
  • Event fields
  • Volatile fields (both reads and writes are considered impure due to their special memory ordering semantics and thread safety implications)

Generic and Advanced Constructs

  • Generic methods
  • Generic type parameters with constraints
  • Covariance and contravariance
  • Reflection
  • Dynamic typing
  • Unsafe code

Enum Operations

PurelySharp treats enums as pure data types. The following operations with enums are considered pure:

  • Accessing enum values
  • Converting enums to their underlying numeric type
  • Comparing enum values
  • Using methods from the Enum class

Note that the analyzer includes special handling for Enum.TryParse<T>() to treat it as a pure method despite using an out parameter.

Examples

public enum Status
{
    Pending,
    Active,
    Completed,
    Failed
}

public class EnumOperations
{
    [EnforcePure]
    public bool IsActiveOrPending(Status status)
    {
        return status == Status.Active || status == Status.Pending;
    }

    [EnforcePure]
    public int GetStatusCode(Status status)
    {
        return (int)status;
    }

    [EnforcePure]
    public bool ParseStatus(string value, out Status status)
    {
        return Enum.TryParse(value, out status);
    }

    [EnforcePure]
    public Status GetStatusFromValue(int value)
    {
        return (Status)value;
    }
}

Delegate Operations

PurelySharp supports delegate types and operations. The purity analysis for delegates focuses on both the creation of delegates and their invocation:

Delegate Purity Rules

  • Delegate Type Definitions: Defining delegate types is always pure.
  • Delegate Creation:
    • Creating a delegate from a pure method is pure.
    • Creating a lambda expression is pure if the lambda body is pure and it doesn't capture impure state.
    • Creating an anonymous method is pure if its body is pure and it doesn't capture impure state.
  • Delegate Invocation:
    • Invoking a delegate is pure if the delegate target is pure and all arguments are pure.
    • If the analyzer can't determine the purity of the delegate target, it conservatively marks the invocation as impure.
  • Delegate Combination:
    • Combining delegates (+=, +) is pure if both delegate operands are pure.
    • Removing delegates (-=, -) is pure if both delegate operands are pure.

Examples

// Define delegate types (always pure)
public delegate int Calculator(int x, int y);
public delegate void Logger(string message);

public class DelegateOperations
{
    // Pure delegate field
    private readonly Func<int, int, int> _adder = (x, y) => x + y;

    [EnforcePure]
    public int Add(int x, int y)
    {
        // Creating and invoking a pure lambda delegate
        Calculator calc = (a, b) => a + b;
        return calc(x, y);
    }

    [EnforcePure]
    public IEnumerable<int> ProcessNumbers(IEnumerable<int> numbers)
    {
        // Using delegates with LINQ (pure)
        return numbers.Where(n => n > 0)
                     .Select(n => n * 2);
    }

    [EnforcePure]
    public Func<int, int> GetMultiplier(int factor)
    {
        // Higher-order function returning a pure delegate
        return x => x * factor;
    }

    [EnforcePure]
    public int CombineDelegates(int x, int y)
    {
        // Combining pure delegates
        Func<int, int> doubler = n => n * 2;
        Func<int, int> incrementer = n => n + 1;

        // Combined delegate is pure if components are pure
        Func<int, int> combined = n => incrementer(doubler(n));

        return combined(x) + combined(y);
    }

    // This would generate a diagnostic
    [EnforcePure]
    public void ImpureDelegateExample()
    {
        int counter = 0;

        // Impure delegate - captures and modifies a local variable
        Action incrementCounter = () => { counter++; };

        // Invoking impure delegate
        incrementCounter(); // Analyzer will flag this
    }
}

Note that delegate invocations are analyzed conservatively. If the analyzer cannot determine the purity of a delegate, it will mark the invocation as impure.

Attributes

  • [EnforcePure] - Marks a method that should be analyzed for purity
  • [AllowSynchronization] - Allows lock statements in pure methods when synchronizing on readonly objects

Impure Namespaces (Always Considered Impure)

  • System.IO
  • System.Net
  • System.Data
  • System.Threading
  • System.Threading.Tasks
  • System.Diagnostics
  • System.Security.Cryptography
  • System.Runtime.InteropServices

Impure Types (Always Considered Impure)

  • Random
  • DateTime
  • File
  • Console
  • Process
  • Task
  • Thread
  • Timer
  • WebClient
  • HttpClient
  • StringBuilder
  • Socket
  • NetworkStream

Common Impure Operations

  • Modifying fields or properties
  • Reading or writing volatile fields
  • Using Interlocked operations
  • Calling methods with side effects
  • I/O operations (file, console, network)
  • Async operations
  • Locking (thread synchronization)
  • Event subscription/raising
  • Unsafe code and pointer manipulation
  • Creating mutable collections

Cross-Framework and Language Version Support

  • C# 8.0+ language features
  • Different target frameworks (.NET Framework, .NET Core, .NET 5+)

Examples

Pure Method Example

The analyzer ensures that methods marked with [EnforcePure] don't contain impure operations:

using System;

[AttributeUsage(AttributeTargets.Method)]
public class EnforcePureAttribute : Attribute { }

public class TestClass
{
    [EnforcePure]
    public int PureHelperMethod(int x)
    {
        return x * 2; // Pure operation
    }

    [EnforcePure]
    public int TestMethod(int x)
    {
        // Call to pure method is also pure
        return PureHelperMethod(x) + 5;
    }
}

Impure Method Examples

The analyzer detects impure operations and reports diagnostics:

Modifying State
using System;

[AttributeUsage(AttributeTargets.Method)]
public class EnforcePureAttribute : Attribute { }

public class TestClass
{
    private int _state;

    [EnforcePure]
    public int TestMethod(int value)
    {
        _state++; // Impure operation: modifies class state
        return _state;
    }
}

// Analyzer Error: PMA0001 - Method 'TestMethod' is marked as pure but contains impure operations
I/O Operations
using System;
using System.IO;

[AttributeUsage(AttributeTargets.Method)]
public class EnforcePureAttribute : Attribute { }

public class TestClass
{
    [EnforcePure]
    public void TestMethod(string path)
    {
        File.WriteAllText(path, "test"); // Impure operation: performs I/O
    }
}

// Analyzer Error: PMA0001 - Method 'TestMethod' is marked as pure but contains impure operations
Console Output
using System;

[AttributeUsage(AttributeTargets.Method)]
public class EnforcePureAttribute : Attribute { }

public class TestClass
{
    [EnforcePure]
    public int TestMethod()
    {
        Console.WriteLine("Hello World"); // Impure operation: console output
        return 42;
    }
}

// Analyzer Error: PMA0001 - Method 'TestMethod' is marked as pure but contains impure operations
Static Field Access
using System;

[AttributeUsage(AttributeTargets.Method)]
public class EnforcePureAttribute : Attribute { }

public class TestClass
{
    private static string sharedState = "";

    [EnforcePure]
    public string TestMethod()
    {
        // Reading from static field is considered impure
        return sharedState;
    }
}

// Analyzer Error: PMA0001 - Method 'TestMethod' is marked as pure but contains impure operations
Volatile Field Access
using System;

[AttributeUsage(AttributeTargets.Method)]
public class EnforcePureAttribute : Attribute { }

public class TestClass
{
    private volatile int _counter;

    [EnforcePure]
    public int GetCounter()
    {
        // Both reading and writing to volatile fields is considered impure
        // due to their special memory ordering semantics
        return _counter;
    }

    [EnforcePure]
    public void UpdateCounter(int value)
    {
        _counter = value; // Writing to volatile field is impure
    }
}

// Analyzer Error: PMA0001 - Method 'GetCounter' is marked as pure but contains impure operations
// Analyzer Error: PMA0001 - Method 'UpdateCounter' is marked as pure but contains impure operations
Thread Synchronization with Volatile Fields
using System;
using System.Threading;

[AttributeUsage(AttributeTargets.Method)]
public class EnforcePureAttribute : Attribute { }
[AttributeUsage(AttributeTargets.Method)]
public class AllowSynchronizationAttribute : Attribute { }

public class TestClass
{
    private volatile bool _initialized;
    private readonly object _lock = new object();

    [EnforcePure]
    [AllowSynchronization] // Even with AllowSynchronization, volatile read is impure
    public void EnsureInitialized()
    {
        if (!_initialized) // Reading volatile field is impure
        {
            lock (_lock)
            {
                if (!_initialized) // Reading volatile field again is impure
                {
                    Initialize();
                    _initialized = true; // Writing to volatile field is impure
                }
            }
        }
    }

    private void Initialize() { /* ... */ }
}

// Analyzer Error: PMA0001 - Method 'EnsureInitialized' is marked as pure but contains impure operations
Atomic Operations with Interlocked
using System;
using System.Threading;

[AttributeUsage(AttributeTargets.Method)]
public class EnforcePureAttribute : Attribute { }

public class TestClass
{
    private volatile int _counter;

    [EnforcePure]
    public int IncrementAndGet()
    {
        // Using Interlocked with volatile fields is impure
        // since it modifies shared state in a thread-safe manner
        return Interlocked.Increment(ref _counter);
    }

    [EnforcePure]
    public int CompareExchange(int newValue, int comparand)
    {
        // All Interlocked operations are impure
        return Interlocked.CompareExchange(ref _counter, newValue, comparand);
    }
}

// Analyzer Error: PMA0001 - Method 'IncrementAndGet' is marked as pure but contains impure operations
// Analyzer Error: PMA0001 - Method 'CompareExchange' is marked as pure but contains impure operations

More Complex Examples

LINQ Operations (Pure)

LINQ operations are generally considered pure as they work on immutable views of data:

using System;
using System.Linq;
using System.Collections.Generic;

[AttributeUsage(AttributeTargets.Method)]
public class EnforcePureAttribute : Attribute { }

public class TestClass
{
    [EnforcePure]
    public IEnumerable<int> FilterAndTransform(IEnumerable<int> numbers)
    {
        // LINQ operations are pure
        return numbers
            .Where(n => n > 10)
            .Select(n => n * 2)
            .OrderBy(n => n);
    }
}

// No analyzer errors - method is pure
Iterator Methods (Pure)

Iterator methods using yield return can be pure:

using System;
using System.Collections.Generic;

[AttributeUsage(AttributeTargets.Method)]
public class EnforcePureAttribute : Attribute { }

public class TestClass
{
    [EnforcePure]
    public IEnumerable<int> GenerateFibonacciSequence(int count)
    {
        int a = 0, b = 1;

        for (int i = 0; i < count; i++)
        {
            yield return a;
            (a, b) = (b, a + b); // Tuple deconstruction for swapping
        }
    }
}

// No analyzer errors - method is pure
Lock Statements with AllowSynchronization

Lock statements are allowed in pure methods when using the [AllowSynchronization] attribute:

using System;

[AttributeUsage(AttributeTargets.Method)]
public class EnforcePureAttribute : Attribute { }

[AttributeUsage(AttributeTargets.Method)]
public class AllowSynchronizationAttribute : Attribute { }

public class TestClass
{
    private readonly object _lockObj = new object();
    private readonly Dictionary<string, int> _cache = new Dictionary<string, int>();

    [EnforcePure]
    [AllowSynchronization]
    public int GetOrComputeValue(string key, Func<string, int> computeFunc)
    {
        lock (_lockObj) // Normally impure, but allowed with [AllowSynchronization]
        {
            if (_cache.TryGetValue(key, out int value))
                return value;

            // Compute is allowed if the function is pure
            int newValue = computeFunc(key);
            _cache[key] = newValue; // This would be impure without [AllowSynchronization]
            return newValue;
        }
    }
}

// No analyzer errors with [AllowSynchronization]
Switch Expressions and Pattern Matching

Modern C# pattern matching and switch expressions are supported:

using System;

[AttributeUsage(AttributeTargets.Method)]
public class EnforcePureAttribute : Attribute { }

public class Shape { }
public class Circle : Shape { public double Radius { get; } }
public class Rectangle : Shape { public double Width { get; } public double Height { get; } }

public class TestClass
{
    [EnforcePure]
    public double CalculateArea(Shape shape)
    {
        // Switch expression with pattern matching
        return shape switch
        {
            Circle c => Math.PI * c.Radius * c.Radius,
            Rectangle r => r.Width * r.Height,
            _ => throw new ArgumentException("Unknown shape type")
        };
    }
}

// No analyzer errors - method is pure
Working with Immutable Collections

Methods that use immutable collections remain pure:

using System;
using System.Collections.Immutable;
using System.Linq;

[AttributeUsage(AttributeTargets.Method)]
public class EnforcePureAttribute : Attribute { }

public class TestClass
{
    [EnforcePure]
    public ImmutableDictionary<string, int> AddToCounters(
        ImmutableDictionary<string, int> counters,
        string key)
    {
        // Working with immutable collections preserves purity
        if (counters.TryGetValue(key, out int currentCount))
            return counters.SetItem(key, currentCount + 1);
        else
            return counters.Add(key, 1);

        // Note: The original collection is not modified
    }
}

// No analyzer errors - method is pure
Complex Method with Multiple Pure Operations

Complex methods combining multiple pure operations:

using System;
using System.Linq;
using System.Collections.Generic;
using System.Collections.Immutable;

[AttributeUsage(AttributeTargets.Method)]
public class EnforcePureAttribute : Attribute { }

public class TestClass
{
    [EnforcePure]
    public (double Average, int Min, int Max, ImmutableList<int> Filtered) AnalyzeData(
        IEnumerable<int> data, int threshold)
    {
        // Local function (must also be pure)
        bool IsOutlier(int value) => value < 0 || value > 1000;

        // Use LINQ to process the data
        var filteredData = data
            .Where(x => !IsOutlier(x) && x >= threshold)
            .ToImmutableList();

        if (!filteredData.Any())
            throw new ArgumentException("No valid data points after filtering");

        // Multiple computations on the filtered data
        var average = filteredData.Average();
        var min = filteredData.Min();
        var max = filteredData.Max();

        // Return a tuple with the results
        return (Average: average, Min: min, Max: max, Filtered: filteredData);
    }
}

// No analyzer errors - method is pure

Limitations and Edge Cases

The following examples demonstrate cases where the analyzer may fail to correctly identify impure operations:

Indirect Method Impurity

Constructor Analysis

When applying [EnforcePure] to constructors, the analyzer applies special rules to account for the unique purpose of constructors. A constructor marked as pure must follow these rules:

  1. Instance field/property assignment is permitted: Unlike regular methods, constructors can assign values to instance fields and properties of the containing type.

  2. Static field modification is not permitted: Modifying static fields is still considered impure, as this affects state beyond the instance being constructed.

  3. Impure method calls are not permitted: Calling impure methods (like I/O operations) from a pure constructor is not allowed.

  4. Collection initialization is permitted: Creating and initializing collections (e.g., new List<int> { 1, 2, 3 }) is allowed if the collection is assigned to an instance field.

  5. Base constructor calls: If a constructor calls a base constructor, the base constructor must also be pure.

Examples

Pure Constructor
[AttributeUsage(AttributeTargets.Constructor)]
public class EnforcePureAttribute : Attribute { }

public class Person
{
    private readonly string _name;
    private readonly int _age;
    private readonly List<string> _skills;

    [EnforcePure]
    public Person(string name, int age)
    {
        _name = name;
        _age = age;
        _skills = new List<string>(); // Allowed: initializing a collection field
    }
}
Impure Constructor (Static Field Modification)
public class Counter
{
    private static int _instanceCount = 0;
    private readonly int _id;

    [EnforcePure]
    public Counter() // Error: Modifies static state
    {
        _id = ++_instanceCount; // Impure: modifies static field
    }
}
Impure Constructor (Calling Impure Methods)
public class Logger
{
    private readonly string _name;

    [EnforcePure]
    public Logger(string name) // Error: Calls an impure method
    {
        _name = name;
        InitializeLog(); // Calls impure method
    }

    private void InitializeLog()
    {
        Console.WriteLine($"Logger {_name} initialized"); // Impure operation
    }
}

Cross-Framework and Language Version Support

Product Compatible and additional computed target framework versions.
.NET net5.0 was computed.  net5.0-windows was computed.  net6.0 was computed.  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 was computed.  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 was computed.  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.
  • .NETStandard 2.0

    • No dependencies.

NuGet packages

This package is not used by any NuGet packages.

GitHub repositories

This package is not used by any popular GitHub repositories.

Version Downloads Last updated
0.0.3 61 5/10/2025
0.0.2 61 5/10/2025
0.0.1 296 4/18/2025