Mapify.NET 2.3.0

There is a newer version of this package available.
See the version list below for details.
dotnet add package Mapify.NET --version 2.3.0
                    
NuGet\Install-Package Mapify.NET -Version 2.3.0
                    
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="Mapify.NET" Version="2.3.0" />
                    
For projects that support PackageReference, copy this XML node into the project file to reference the package.
<PackageVersion Include="Mapify.NET" Version="2.3.0" />
                    
Directory.Packages.props
<PackageReference Include="Mapify.NET" />
                    
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 Mapify.NET --version 2.3.0
                    
#r "nuget: Mapify.NET, 2.3.0"
                    
#r directive can be used in F# Interactive and Polyglot Notebooks. Copy this into the interactive tool or source code of the script to reference the package.
#:package Mapify.NET@2.3.0
                    
#:package directive can be used in C# file-based apps starting in .NET 10 preview 4. Copy this into a .cs file before any lines of code to reference the package.
#addin nuget:?package=Mapify.NET&version=2.3.0
                    
Install as a Cake Addin
#tool nuget:?package=Mapify.NET&version=2.3.0
                    
Install as a Cake Tool

Mapify.NET

Tests Build

Mapify is a lightweight .NET library for creating static mapping expressions for C# objects. It bridges the gap between in-memory object mapping and LINQ projections (e.g., Entity Framework), allowing you to reuse the same mapping logic consistently across your application.

Features ✨

  • Zero Boilerplate: Automatically maps properties with compatible names and types.
  • Entity Framework Compatible: Generates expression trees compatible with IQueryable projections.
  • Safe: Handles null checks and type conversions automatically.
  • Flexible: Supports explicit overrides and partial mappings.
  • Performance: Caches compiled delegates for in-memory mapping.

Installation 📦

Install via NuGet:

dotnet add package Mapify.NET

Supported Frameworks 📋

  • .NET 8.0, 9.0, 10.0
  • .NET Standard 2.0, 2.1
  • .NET Framework 4.6.2+

Getting Started 🚀

1. Define your Classes

public class Address {
    public string Street { get; set; }
    public string City { get; set; }
}

public class AddressDto {
    public string Street { get; set; }
    public string City { get; set; }
}

public class Person {
    public string FirstName { get; set; }
    public string LastName { get; set; }
    public Address MainAddress { get; set; }
    public ICollection<Address> Addresses { get; set; }
}

public class PersonDto {
    public string Name { get; set; }
    public AddressDto MainAddress { get; set; }
    public ICollection<AddressDto> Addresses { get; set; }
}

The recommended approach is profile + instance mapper (IMapify). It keeps mapping configuration explicit and is easier to test.

using Mapify.NET;
using Microsoft.Extensions.DependencyInjection;

public class PersonProfile : MapifyProfile {
    protected override void Configure() {
        CreateMap<Person, PersonDto>();
    }
}

// Use UseMap<TSource, TTarget>(sourceMember) to explicitly mark that
// a property should be mapped via an existing registered map.
public class QueryProfile : MapifyProfile {
    protected override void Configure() {
        CreateMap<Address, AddressDto>();

        CreateMap<Person, PersonDto>(p => new PersonDto {
            Name = p.FirstName + " " + p.LastName,
            MainAddress = UseMap<Address, AddressDto>(p.MainAddress)
        });
    }
}

// Named maps: same source/target pair, different map names.
public class PersonValueProfile : MapifyProfile {
    protected override void Configure() {
        // default map for Person -> string
        CreateMap<Person, string>(x => x.FirstName);

        // named maps for the same Person -> string type pair
        CreateMap<Person, string>("FullName", x => x.FirstName + " " + x.LastName);
        CreateMap<Person, string>("Initials", x => x.FirstName.Substring(0, 1) + x.LastName.Substring(0, 1));
    }
}

// UseMap also supports named maps in profile initializers.
public class StudentProfile : MapifyProfile {
    protected override void Configure() {
        CreateMap<Student, StudentDto>("Raw", x => new StudentDto { Name = x.Name });
        CreateMap<Student, StudentDto>("Upper", x => new StudentDto { Name = x.Name.ToUpper() });

        CreateMap<Classroom, ClassroomDto>(x => new ClassroomDto {
            Students = UseMap<IEnumerable<Student>, IEnumerable<StudentDto>>("Upper", x.Students)
        });
    }
}

// If source and destination property names differ, pass the source explicitly.
public class NumberProfile : MapifyProfile {
    protected override void Configure() {
        CreateMap<NumberSource, NumberDto>();

        CreateMap<Order, OrderDto>(x => new OrderDto {
            // Order.SourceNumber -> OrderDto.Number
            Number = UseMap<NumberSource, NumberDto>(x.SourceNumber)
        });
    }
}

var services = new ServiceCollection();

// Scans the given assemblies for all IMapifyProfile implementations,
// registers them, and registers IMapify as singleton.
services.AddMapify(typeof(PersonProfile).Assembly);

// Scans the given assemblies for all IMapifyProfile implementations,
// registers them, and registers IMapify as Scoped.
services.AddMapify(ServiceLifetime.Scoped, typeof(PersonProfile).Assembly);

// Manually add profiles of the given assembly
// registers IMapify as Scoped without adding profiles
services.AddMapifyProfiles(typeof(PersonProfile).Assembly);
services.AddMapify(ServiceLifetime.Scoped);

// Register a specific profile manually
services.AddMapifyProfile<PersonProfile>();
services.AddMapify(ServiceLifetime.Scoped);

// Named mapper with isolated profile set
services.AddMapifyProfile<PersonProfile>("queries");
services.AddMapifyNamed("queries", ServiceLifetime.Transient);

var provider = services.BuildServiceProvider();
var defaultMapper = provider.GetRequiredService<IMapify>();
var queryMapper = provider.GetMapify("queries");

CreateMap<TSource, TTarget>(...) inside MapifyProfile is registration-only. Map building is deferred until all profiles are registered, so unordered registrations are supported.

When you need explicit nested map usage in a profile initializer, use UseMap<TSource, TTarget>(x.SourceMember). During build, Mapify resolves the dependency to the registered map (including nullable variants).

To force a specific named map, use UseMap<TSource, TTarget>("Name", x.SourceMember).

To explicitly ignore a destination property in a partial map, use Ignore<T>():

CreateMap<Person, PersonDto>(x => new PersonDto {
    Name = x.FirstName + " " + x.LastName,
    InternalCode = Ignore<string>()
});

Ignore behavior:

  • For new object mapping (Map(source) / projections), ignored properties are not mapped from source.
  • If an ignored destination property is marked required:
    • Mapify preserves an existing class/constructor initializer when present.
    • Otherwise, Mapify emits the configured fallback (default(T) for non-collections, empty fallback for non-nullable collections).
  • For map-to-existing (Map(source, existing)), ignored properties are left unchanged on the target instance.

You can also chain LINQ operators after UseMap, for example:

CreateMap<Person, PersonDto>(x => new PersonDto {
    Addresses = UseMap<IEnumerable<Address>, IEnumerable<AddressDto>>(x.Addresses)
        .OrderBy(dto => dto.StreetName)
});

UseMap also supports arrays and enumerable types. If a map exists for element types (TSrc -> TDest), you can use it for collection shapes like TSrc[] -> TDest[] and IEnumerable<TSrc> -> IEnumerable<TDest>.

This also applies to named maps: if UseMap is called with a name for a collection shape, Mapify resolves the named map for the individual element types.

CreateMap<Address, AddressDto>("Postal", x => new AddressDto {
    StreetName = x.StreetName
});

CreateMap<Person, PersonDto>(x => new PersonDto {
    Addresses = UseMap<IEnumerable<Address>, IEnumerable<AddressDto>>("Postal", x.Addresses)
        .OrderBy(dto => dto.StreetName)
});

In this example, Mapify applies the named element map Address -> AddressDto with name "Postal" for each item.

Recursive UseMap depth

For self-referencing or cyclic object graphs, Mapify expands recursive UseMap calls to a finite depth.

  • If no depth is specified, the default recursion depth is 6.
  • You can override depth per marker with:
    • UseMap<TSource, TTarget>(source, maxDepth)
    • UseMap<TSource, TTarget>("Name", source, maxDepth)
  • maxDepth must be a constant positive integer in the expression.

Mapify also enforces a hard safety cap during map build:

  • Default hard cap: 10
  • Configure per mapper instance: mapify.UseMaxRecursiveMapBuildDepth(value)
  • If any marker depth exceeds the hard cap, map building throws InvalidOperationException.

ProjectTo<TTarget>() markers inside CreateMap expressions are rewritten to the same internal behavior, so the same recursive depth rules apply.

3. Use the instance mapper in-memory

var mapper = provider.GetRequiredService<IMapify>();

var dto = mapper.Map<Person, PersonDto>(person);

// named map execution
var fullName = mapper.Map<Person, string>(person, "FullName");

var existing = new PersonDto();
mapper.Map(person, existing);

// named map-to-existing execution
mapper.Map(person, existing, "SomeNamedMap");

4. Use the instance mapper with Entity Framework (IQueryable)

IMapify.GetMap<TSource, TTarget>() returns the expression for projections, or null if missing (and default-map fallback is disabled). Use IMapify.GetRequiredMap<TSource, TTarget>() when missing maps should throw.

The static Mapper API follows the same pattern:

  • Mapper.GetMap<TSource, TTarget>() may return null
  • Mapper.GetRequiredMap<TSource, TTarget>() throws when missing

For convenience, you can also call ProjectTo<TTarget>() directly on an IQueryable. Use the overload with IMapify to resolve instance-based profile maps:

var people = await _dbContext.Persons
    .ProjectTo<PersonDto>(_mapify)
    .OrderBy(x => x.Name)
    .ToArrayAsync(cancellationToken);

Named map projections are supported as well:

var people = await _dbContext.Persons
    .ProjectTo<PersonDto>(_mapify, "Masked")
    .ToArrayAsync(cancellationToken);

If you use the static Mapper API (Mapper.AddMap(...)), you can call:

var people = _dbContext.Persons
    .ProjectTo<PersonDto>()
    .OrderBy(x => x.Name)
    .ToArray();

For named maps, both APIs follow the same rule:

  • GetMap<TSource, TTarget>("Name") may return null when the named map is missing
  • GetRequiredMap<TSource, TTarget>("Name") throws when the named map is missing

UseMap works with expressions too (not only direct properties). For example, you can filter children before mapping:

public class StudentProfile : MapifyProfile {
    protected override void Configure() {
        CreateMap<Student, StudentDto>();

        CreateMap<Classroom, ClassroomDto>(x => new ClassroomDto {
            Students = UseMap<IEnumerable<Student>, IEnumerable<StudentDto>>(
                x.Students.Where(s => s.Name != null)
            )
        });
    }
}
public async Task<IEnumerable<PersonDto>> GetPersonDtosAsync(int skip, int take, CancellationToken cancellationToken = default) {
    var mapExpr = _mapify.GetMap<Person, PersonDto>();

    return await _dbContext.Persons
        .Select(mapExpr)
        .OrderBy(x => x.Name)
        .Skip(skip)
        .Take(take)
        .ToArrayAsync(cancellationToken);
}

The same pattern works for named maps as well:

Students = UseMap<IEnumerable<Student>, IEnumerable<StudentDto>>(
    "Upper",
    x.Students.Where(s => s.Name != null)
)

UseMap can also be used inside calculations. For example, map measurements first, then take the maximum Fahrenheit value and convert it to Celsius:

public class MeasurementProfile : MapifyProfile {
    protected override void Configure() {
        CreateMap<Measurement, MeasurementDto>();

        CreateMap<WeatherSample, WeatherSampleDto>(x => new WeatherSampleDto {
            MaxTemperatureCelsius = (
                UseMap<IEnumerable<Measurement>, IEnumerable<MeasurementDto>>(x.Measurements)
                    .OrderBy(m => m.Fahrenheit)
                    .Max(m => m.Fahrenheit)
                - 32m) * 5m / 9m
        });
    }
}

Chaining also works for named maps:

Addresses = UseMap<IEnumerable<Address>, IEnumerable<AddressDto>>("Postal", x.Addresses)
    .OrderBy(dto => dto.StreetName)

For EF/EF Core projections, if you apply ordering or other sequence operators after UseMap, materialize the sequence for stable provider translation:

Addresses = UseMap<IEnumerable<Address>, IEnumerable<AddressDto>>(x.Addresses)
    .OrderBy(dto => dto.StreetName)
    .ToList()

Inside CreateMap expressions you can also use ProjectTo<TTarget>() on enumerable members. Mapify rewrites this marker to the same internal behavior as UseMap.

CreateMap<Person, PersonDto>(x => new PersonDto {
    Addresses = x.Addresses.ProjectTo<AddressDto>().ToArray()
});

The marker also supports named mappings:

CreateMap<Person, PersonDto>("Masked", x => new PersonDto {
    Addresses = x.Addresses.ProjectTo<AddressDto>("Postal").ToArray()
});

Detailed Functionality 📚

Implicit Mappings

When CreateMap<TSource, TTarget>() is called, Mapify automatically generates bindings for properties where:

  1. Names Match: Source and Destination property names are identical.

  2. Types are Compatible:

    • Exact match.
    • Target is assignable from Source.
    • Nullable Handling:
      • TT? (Implicit cast)
      • T?T (Uses source value if not null, otherwise default(T))

    If a map already exists for a same-name property type pair (e.g. Address -> AddressDto), Mapify uses that map implicitly before falling back to direct assignment.

Mapping Type Resolution Precedence

When Mapify tries to resolve a nested map (implicit same-name property mapping or UseMap), it follows this precedence:

  1. Exact source/target type match
  2. Nullable underlying-type fallback
  3. Assignable collection-pair fallback (e.g. IEnumerable<T> map reused for List<T> members)
  4. Collection element-type fallback (only when both sides are collection shapes)
1) Exact map wins

If both are registered, exact type map is preferred.

CreateMap<NumberSource, NumberDto>(x => new NumberDto { Value = x.Value + 1 });
CreateMap<NumberSource?, NumberDto?>(x => x == null ? null : new NumberDto { Value = x.Value.Value + 100 });

CreateMap<Order, OrderDto>(); // Order.Number: NumberSource? -> OrderDto.Number: NumberDto?

Order.Number uses NumberSource? -> NumberDto? (exact), not the non-nullable fallback map.

2) Nullable fallback when exact nullable map is missing

If only NumberSource -> NumberDto exists, Mapify can lift/adapt it for nullable variants where compatible.

CreateMap<NumberSource, NumberDto>(x => new NumberDto { Value = x.Value + 1 });
CreateMap<Order, OrderDto>(); // NumberSource? -> NumberDto?

Mapify uses the non-nullable map and injects null-safe adaptation.

3) Collection map precedence

For collection properties, Mapify resolves in this order:

  1. exact collection-pair map
  2. assignable collection-pair map (for example IEnumerable<TSrc> -> IEnumerable<TDest>)
  3. element map fallback
CreateMap<Item, ItemDto>(x => new ItemDto { Value = x.Value + 1 });
CreateMap<List<Item>, List<ItemDto>>(x => x.Select(i => new ItemDto { Value = i.Value + 100 }).ToList());

CreateMap<Batch, BatchDto>(); // List<Item> -> List<ItemDto>

Batch.Items uses List<Item> -> List<ItemDto> (exact collection map). If that map is removed but an IEnumerable<Item> -> IEnumerable<ItemDto> map exists, that map is used and materialized to the destination collection type. If neither collection map exists, Mapify falls back to Item -> ItemDto per element.

For nullable collection members, Mapify preserves null safely for supported query/provider shapes.

4) Collection fallback + initializer preservation

When Mapify needs a fallback value for a collection member (for example source is null, or destination member has no matching source member), it applies these rules:

  1. If the destination member is already initialized in the class/constructor, that user-defined value is preserved.
  2. Otherwise, if the destination collection is non-nullable (or marked required), Mapify uses an empty collection/array fallback where possible.
  3. Otherwise (nullable destination collection), Mapify uses null.

For non-collection members, fallback remains default(T).

In short:

  • source member exists → map from source value
  • source member missing → keep user initializer when present, otherwise fallback by nullability/required rules
  • source value is null + non-nullable collection destination → empty collection fallback

If a custom collection type implements multiple different IEnumerable<T> element types, Mapify throws a descriptive configuration error because element type inference would be ambiguous.

GetMap / GetRequiredMap exact-type rule

GetMap<TSource, TTarget>() and GetRequiredMap<TSource, TTarget>() always resolve by the exact requested pair. They do not return a different pair's map (for example, element maps or nullable underlying-type maps).

Examples:

  • If only Item -> ItemDto is registered, GetMap<List<Item>, List<ItemDto>>() is null (unless default-map fallback is enabled, in which case Mapify creates a new exact List<Item> -> List<ItemDto> expression).
  • If only NumberSource -> NumberDto is registered, GetMap<NumberSource?, NumberDto?>() is null (same default fallback behavior applies).

This keeps GetMap/GetRequiredMap type-safe: the returned expression always matches the exact generic types requested.

Named Mappings

Named mappings let you register multiple mappings for the same source/target type pair.

public class PersonValueProfile : MapifyProfile {
    protected override void Configure() {
        CreateMap<Person, string>("FullName", x => x.FirstName + " " + x.LastName);
        CreateMap<Person, string>("Initials", x => x.FirstName.Substring(0, 1) + x.LastName.Substring(0, 1));
    }
}

var fullName = mapify.Map<Person, string>(person, "FullName");
var initials = mapify.Map<Person, string>(person, "Initials");

Rules:

  • A default map and named maps can coexist for the same TSource -> TTarget.
  • Each (TSource, TTarget, Name) combination must be unique.
  • UseMap can target named maps via UseMap<TSource, TTarget>("Name", sourceExpression).

Static API (advanced scenarios)

The static Mapper API is still fully supported, but typically used in advanced scenarios.

Static map declarations
using Mapify.NET;
using System.Linq.Expressions;

public static class PersonMappings {
    public static readonly Expression<Func<Person, PersonDto>> PersonToPersonDto =
        Mapper.CreateMap<Person, PersonDto>(p => new PersonDto {
            Name = $"{p.FirstName} {p.LastName}",
            MainAddress = AddressMappings.AddressToAddressDto.Invoke(p.MainAddress)
        });
}

public static class AddressMappings {
    public static readonly Expression<Func<Address, AddressDto>> AddressToAddressDto =
        Mapper.CreateMap<Address, AddressDto>();
}
Static API with Entity Framework + LINQKit

If your static expressions use .Invoke(...), combine with LINQKit and .AsExpandable().

public async Task<IEnumerable<PersonDto>> GetPersonDtosAsync(int skip, int take, CancellationToken cancellationToken = default) {
    return await _dbContext.Persons
        .AsExpandable()
        .Select(PersonMappings.PersonToPersonDto)
        .OrderBy(x => x.Name)
        .Skip(skip)
        .Take(take)
        .ToArrayAsync(cancellationToken);
}

AsExpandable() is the key piece that lets EF translate expressions that use .Invoke().

Use the package that matches your scenario:

  • LinqKit.Core: expression composition utilities (PredicateBuilder, Invoke, Expand) without EF integration.
  • LinqKit or LinqKit.EntityFramework: for Entity Framework 6.x.
  • LinqKit.Microsoft.EntityFrameworkCore: for Entity Framework Core.

For EF Core, the package ID stays the same (LinqKit.Microsoft.EntityFrameworkCore), but the major version should match EF Core major.

Global Configuration & Static Maps

You can register mappings globally to use the static Mapper.Map convenience methods.

// Register a map globally
Mapper.AddMap(PersonMappings.PersonToPersonDto);

// Or create and add in one step
Mapper.CreateAndAddMap<Person, PersonDto>(p => new PersonDto { ... });

// Use the global map anywhere
var dto = Mapper.Map<Person, PersonDto>(person);

Value Mappings (non-initializer)

Mapify also supports mappings where the expression returns a value directly (not new TTarget { ... }).

Mapper.AddMap<Person, string>(x => x.FirstName);
Mapper.AddMap<SourceStatus, TargetStatus>(x => x == SourceStatus.Active ? TargetStatus.Enabled : TargetStatus.Disabled);

var name = Mapper.Map<Person, string>(person);
var status = Mapper.Map<SourceStatus, TargetStatus>(SourceStatus.Active);

Note: value mappings are supported for Map(source) only. Map(source, target) requires an object-initializer mapping.

Explicit Overrides & Coalescing

You can provide a partial initializer to override specific properties. Mapify also rewrites null-coalescing operators (??) to conditional expressions (x != null ? x : y) to ensure compatibility with all LINQ providers (some EF versions struggle with ??).

Mapper.CreateMap<Person, PersonDto>(p => new PersonDto {
    // Explicit override
    Name = p.FirstName + " " + p.LastName,
    
    // Coalescing is rewritten for EF compatibility
    Region = p.Region ?? "Unknown" 
});

Caching & Performance ⚡

  • Compiled Delegates: Accessors are compiled to delegates.
  • Strategy:
    • Extension Method (.Map()): Caches the compiled delegate for that specific expression instance.
    • Static Mapper.Map: Uses a global cache.
      • Priority: Explicitly added maps (AddMap) take precedence over implicitly generated ones.
      • Auto-Cache: If global fallback is enabled via Mapper.UseDefaultMapIfTypeMapIsMissing(true) before adding a custom map, a default map is generated and cached. Calling AddMap later overwrites this cache entry with your custom definition.

Advanced Usage 🛠️

For advanced scenarios, Mapify exposes several lower-level methods.

Compiling Mappers Manually

If you need high-performance bulk mapping to existing objects and want to manage the delegate lifecycle yourself (referencing Action<TSource, TTarget>), you can use CompileMapper.

// Get the map expression
var mapExpr = PersonMappings.PersonToPersonDto;

// Compile to an Action<Person, PersonDto>
Action<Person, PersonDto> mapAction = Mapper.CompileMapper(mapExpr);

// Use it in a hot loop (zero dictionary lookups)
var target = new PersonDto();
foreach (var item in largeCollection) {
    mapAction(item, target);
    // ...
}

Retrieving Maps

You can retrieve registered maps using GetMap. This is useful in generic or dynamic contexts where you don't have direct access to the static field. GetMap returns null if no map exists and default-map fallback is disabled. If you want throwing behavior, use GetRequiredMap.

// Retrieve a registered map (or null)
var mapExpr = Mapper.GetMap<Person, PersonDto>();

// Enable global fallback once (if desired)
Mapper.UseDefaultMapIfTypeMapIsMissing(true);

// Then GetMap/GetRequiredMap can use generated default maps when missing
var fallbackMapExpr = Mapper.GetMap<Person, PersonDto>();

// Throw if the map is missing
var requiredMapExpr = Mapper.GetRequiredMap<Person, PersonDto>();

Strict Mode Configuration

By default, strict mode is enabled for global maps, meaning Mapper.Map<S, T>(src) throws if no map is registered. You can disable this to allow automatic fallback to default maps globally, though explicit registration is recommended for performance and control.

// Allow implicit generation of default maps if no custom map is found
Mapper.UseDefaultMapIfTypeMapIsMissing(true);

Contributing 🤝

Contributions are welcome! Please feel free to submit a Pull Request.

License 📄

This project is licensed under the MIT License - see the LICENSE file for details.

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 is compatible.  net8.0-android was computed.  net8.0-browser was computed.  net8.0-ios was computed.  net8.0-maccatalyst was computed.  net8.0-macos was computed.  net8.0-tvos was computed.  net8.0-windows was computed.  net9.0 is compatible.  net9.0-android was computed.  net9.0-browser was computed.  net9.0-ios was computed.  net9.0-maccatalyst was computed.  net9.0-macos was computed.  net9.0-tvos was computed.  net9.0-windows was computed.  net10.0 is compatible.  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 is compatible. 
.NET Framework net461 was computed.  net462 is compatible.  net463 was computed.  net47 was computed.  net471 was computed.  net472 was computed.  net48 was computed.  net481 was computed. 
MonoAndroid monoandroid was computed. 
MonoMac monomac was computed. 
MonoTouch monotouch was computed. 
Tizen tizen40 was computed.  tizen60 was computed. 
Xamarin.iOS xamarinios was computed. 
Xamarin.Mac xamarinmac was computed. 
Xamarin.TVOS xamarintvos was computed. 
Xamarin.WatchOS xamarinwatchos was computed. 
Compatible target framework(s)
Included target framework(s) (in package)
Learn more about Target Frameworks and .NET Standard.

NuGet packages

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
3.1.8 94 5/31/2026
3.1.7 145 5/4/2026
3.1.6 93 5/3/2026
3.1.5 163 4/20/2026
3.1.4 95 4/20/2026
3.1.3 105 4/7/2026
3.1.2 93 4/7/2026
3.1.1 93 4/7/2026
3.1.0 94 4/7/2026
3.0.0 117 3/24/2026
2.3.2 105 3/18/2026
2.3.1 113 3/16/2026
2.3.0 263 3/4/2026
2.2.0 107 2/27/2026
2.1.0 104 2/27/2026
2.0.1 103 2/27/2026
2.0.0 106 2/26/2026
1.5.6 101 2/26/2026
1.5.5 102 2/25/2026
1.5.4 97 2/25/2026
Loading failed