Pitasoft.Error 5.3.14

dotnet add package Pitasoft.Error --version 5.3.14
                    
NuGet\Install-Package Pitasoft.Error -Version 5.3.14
                    
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="Pitasoft.Error" Version="5.3.14" />
                    
For projects that support PackageReference, copy this XML node into the project file to reference the package.
<PackageVersion Include="Pitasoft.Error" Version="5.3.14" />
                    
Directory.Packages.props
<PackageReference Include="Pitasoft.Error" />
                    
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 Pitasoft.Error --version 5.3.14
                    
#r "nuget: Pitasoft.Error, 5.3.14"
                    
#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 Pitasoft.Error@5.3.14
                    
#: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=Pitasoft.Error&version=5.3.14
                    
Install as a Cake Addin
#tool nuget:?package=Pitasoft.Error&version=5.3.14
                    
Install as a Cake Tool

Pitasoft.Error

NuGet version NuGet downloads Build Status License .NET Support

Pitasoft.Error is a class library designed to facilitate the handling and management of error collections in .NET applications. It allows grouping errors by property, handling change notifications, and serializing/deserializing error collections to/from JSON.

English | Español


English

Key Features

  • ErrorCollection: A robust collection for storing errors associated with property names.
  • Factory Properties: Access ErrorCollection.Empty for a fresh, empty collection instance.
  • IErrorCollectionContainer: A standard interface for objects (like ViewModels) that expose an ErrorCollection.
  • NuGet Package Scope: The NuGet package contains the class library only (no UI sample applications are shipped inside the package).
  • High Performance: Optimized for low memory allocation and fast execution, especially in filtering and mass clearing operations. It uses [MethodImpl(MethodImplOptions.AggressiveInlining)] on critical paths and has been refactored to eliminate LINQ overhead in core methods.
  • Advanced Filtering: Flexible error retrieval using Intersect or Except operations with JointActionErrors.
  • INotifyDataErrorInfo Support: Fully implements INotifyDataErrorInfo for seamless integration with WPF, Avalonia UI, MAUI, and other data-binding frameworks.
  • Notifications: Support for events when the error collection changes.
  • Total Error Counting: The Count property returns the total number of individual error messages across all properties.
  • JSON Serialization: Built-in extensions to convert error collections to JSON and vice versa.
  • Safer Construction and Deserialization: Constructor copies are list-safe (no shared mutable lists), and JSON deserialization ignores invalid entries (empty keys or null/empty error lists).
  • Lambda-based Extensions: Type-safe property selection via Expression<Func<T, object?>> extensions (no magic strings).
  • Multi-target Support: Compatible with .NET 8.0, .NET 9.0, and .NET 10.0.
  • Fluent API: Chainable methods for fast and readable error collection setup.
  • Exception Error Detection: Automatic error message extraction from complex exception hierarchies.

Installation

You can install this package via NuGet:

dotnet add package Pitasoft.Error

Package Scope and UI Compatibility

  • Pitasoft.Error NuGet package ships only the error-management library.
  • The package is designed to work with INotifyDataErrorInfo, so it is compatible with WPF, MAUI, Avalonia UI, and other MVVM-friendly UI frameworks.

What's New / Current Capabilities

  • ErrorCollection.Empty returns a new instance each time (no shared mutable singleton).
  • Count returns the total number of individual messages across all properties.
  • Clear(...) and ClearAll(...) are the preferred APIs (Remove* methods are kept only for backward compatibility and are marked obsolete).
  • Lambda-based extensions remove magic strings (Add<T>, AnyError<T>, Clear<T>, and Create<T>).
  • JSON serialization can be customized with SetJsonSerializerOptions(...) for the current async flow without changing process-wide defaults.
  • Constructors and JSON converter normalize/copy data to avoid shared list references and invalid entries.
  • null inputs in constructors and bulk APIs are treated defensively as empty/no-op, and null/empty/whitespace error messages are ignored.
  • Combine(...) merges nullable collections without extra null checks (returns null only when both collections are null/empty).
  • WithPrefix(...) creates a new collection with prefixed keys (useful for nested objects like Address.Street).
  • Exception-based APIs are shallow by default; pass deep: true when you want to include inner exceptions recursively.
Newly Added Behaviors (Detailed)
1) Safe copy semantics in constructors

When you build a collection from existing dictionaries/lists, internal lists are copied to avoid accidental shared mutations.

var source = new List<string> { "E1" };
var ec = new ErrorCollection(new[]
{
    new KeyValuePair<string, List<string>>("Name", source)
});

source.Add("E2"); // does not mutate ec
2) Defensive JSON deserialization

Deserialization now ignores invalid entries (empty keys, null lists, empty lists) and keeps only valid errors.

var json = "{\"Valid\":[\"E1\"],\"Empty\":[],\"Null\":null,\" \":[\"Ignored\"]}";
var ec = json.ToErrorCollection();

// Only "Valid" remains
3) Null/empty JSON input is safe

ToErrorCollection always returns a non-null instance for null/empty input.

string? raw = null;
var ec = raw.ToErrorCollection(); // empty collection, never null
4) Defensive null handling in bulk APIs

Bulk add, clear, and filter operations treat null inputs as empty/no-op instead of throwing.

var ec = new ErrorCollection();

ec.Add("Name", (string[]?)null);          // no-op
ec.Clear((IEnumerable<string>?)null);     // no-op
var all = ec.GetErrors(JointActionErrors.Except, (string[]?)null);
5) Exception handling defaults are explicit

Exception-based creation and conversion methods add only the top-level exception message by default. Use deep: true to include the full inner-exception chain.

var shallow = ErrorCollection.Create(ex);            // top-level message only
var deep = ErrorCollection.Create(ex, deep: true);   // includes inner exceptions

Backend Usage (Minimal API / Web API)

Pitasoft.Error is also useful in backend services to standardize validation and error payloads.

using Microsoft.AspNetCore.Mvc;
using Pitasoft.Error;

var builder = WebApplication.CreateBuilder(args);
var app = builder.Build();

app.MapPost("/users", ([FromBody] CreateUserRequest request) =>
{
    var errors = ErrorCollection.Empty;

    if (string.IsNullOrWhiteSpace(request.Email))
        errors.Add(nameof(request.Email), "Email is required.");

    if (!request.AcceptTerms)
        errors.Add(nameof(request.AcceptTerms), "Terms must be accepted.");

    if (errors.HasErrors)
        return Results.ValidationProblem(
            errors.ToDictionary(kv => kv.Key, kv => kv.Value.ToArray()));

    return Results.Created($"/users/{Guid.NewGuid()}", new { ok = true });
});

app.Run();

public record CreateUserRequest(string? Email, bool AcceptTerms);

Basic Usage

Quick API Reference
API Purpose Example
ErrorCollection.Empty Create a fresh empty collection var errors = ErrorCollection.Empty;
Add(...) Add one or more errors errors.Add("Email", "Required");
Clear(...) / ClearAll() Remove errors by property or all errors.Clear("Email");
AnyError(...) Fast checks for existing errors errors.AnyError("Email");
GetErrors(...) Read error messages errors.GetErrors();
GetProperties(...) Read properties with errors errors.GetProperties();
FirstOrDefault(...) Get first error globally or by property errors.FirstOrDefault("Email");
ToJson() / ToErrorCollection() Serialize/deserialize errors var json = errors.ToJson();
Add<T>(...) / Clear<T>(...) Type-safe lambda helpers errors.Add<UserVm>(x => x.Email, "Required");
ErrorCollection.Create(...) Factory methods for fast creation var e = ErrorCollection.Create("General", "Fail");
Quick Error Access
// Get the first error message across all properties (useful for summary UI)
string? firstError = errors.FirstOrDefault();

// Check for errors on a single property (fast O(1) check)
bool hasNameErrors = errors.AnyError("Name");
Creating an Error Collection and Adding/Clearing Errors
using Pitasoft.Error;

// Create using constructor or factory property
var errors = ErrorCollection.Empty;

// Fluent API: chain additions seamlessly
errors.Add("Name", "The name is required.")
      .Add("Email", "The email format is invalid.");

// Count returns the total number of individual error messages (2 in this case)
int totalErrors = errors.Count;

// Support for multiple errors at once (params string[])
errors.Add("Status", "Invalid value", "Out of range");

if (errors.AnyError())
{
    // Handle errors globally
}

// Clear errors for a specific property
errors.Clear("Name");

// Clear all errors
errors.ClearAll();

Note: The Remove and RemoveAll methods are now deprecated in favor of Clear and ClearAll to maintain naming consistency with standard .NET collections.

Methods Without Notifications

If you need to add errors without triggering the ErrorsChanged event:

errors.AddWithoutNotification("SilentProperty", "This error won't trigger an event.");
UI Frameworks Integration (MVVM)

ErrorCollection is designed to be easily integrated into ViewModels. You can implement IErrorCollectionContainer to provide a standard way of exposing errors:

public class MyViewModel : INotifyDataErrorInfo, IErrorCollectionContainer
{
    public ErrorCollection Errors { get; set; } = new();

    public event EventHandler<DataErrorsChangedEventArgs>? ErrorsChanged
    {
        add => Errors.ErrorsChanged += value;
        remove => Errors.ErrorsChanged -= value;
    }

    public System.Collections.IEnumerable GetErrors(string? propertyName) => 
        ((INotifyDataErrorInfo)Errors).GetErrors(propertyName);

    public bool HasErrors => Errors.HasErrors;

    private string _name;
    public string Name
    {
        get => _name;
        set
        {
            _name = value;
            ValidateName();
        }
    }

    private void ValidateName()
    {
        Errors.Clear(nameof(Name));
        if (string.IsNullOrWhiteSpace(Name))
            Errors.Add(nameof(Name), "Name is required");
    }
}
INotifyDataErrorInfo Compatibility Example (WPF / MAUI / Avalonia)

ErrorCollection already implements INotifyDataErrorInfo, so your ViewModel can delegate directly:

public sealed class LoginViewModel : INotifyDataErrorInfo
{
    public ErrorCollection Errors { get; } = new();

    public bool HasErrors => Errors.HasErrors;

    public event EventHandler<DataErrorsChangedEventArgs>? ErrorsChanged
    {
        add => Errors.ErrorsChanged += value;
        remove => Errors.ErrorsChanged -= value;
    }

    public System.Collections.IEnumerable GetErrors(string? propertyName) =>
        ((INotifyDataErrorInfo)Errors).GetErrors(propertyName);

    public void ValidateUser(string? userName)
    {
        Errors.Clear(nameof(UserName));
        if (string.IsNullOrWhiteSpace(userName))
        {
            Errors.Add(nameof(UserName), "User name is required.");
        }
    }

    public string? UserName { get; set; }
}
Advanced Filtering

You can filter errors using JointActionErrors to include or exclude specific properties:

// Get errors only for specific properties
var relevantErrors = errors.GetErrors(JointActionErrors.Intersect, "Username", "Password");

// Get errors for all properties EXCEPT the ones specified
var otherErrors = errors.GetErrors(JointActionErrors.Except, "IsAcceptedTerms");

// Check if any error exists for specific properties
bool hasCriticalErrors = errors.AnyError(JointActionErrors.Intersect, "System", "Database");
Using Static Creation Methods
var errors = ErrorCollection.Create("General", "An unexpected error has occurred.");
JSON Serialization
using Pitasoft.Error.Extensions;

string json = errors.ToJson();
Deserialization from JSON
string json = "{\"name\":[\"Error 1\"]}";
var errors = json.ToErrorCollection();
Lambda-friendly Extensions

These helpers live in the Pitasoft.Error.Extensions namespace.

using Pitasoft.Error;
using Pitasoft.Error.Extensions;

var errors = new ErrorCollection();

// Add errors without magic strings
errors.Add<MyViewModel>(vm => vm.Name, "Name is required");
errors.Add<MyViewModel>(vm => vm.Email, "Invalid format", "Domain not allowed");

// Query errors
bool hasEmailErrors = errors.AnyError<MyViewModel>(vm => vm.Email);

// Joint checks (Intersect/Except)
bool anyUserFieldHasErrors = errors.AnyError<MyViewModel>(JointActionErrors.Intersect,
    vm => vm.Name, vm => vm.Email);

// Clear by property expression
errors.Clear<MyViewModel>(vm => vm.Email);
errors.Clear<MyViewModel>(vm => vm.Name, vm => vm.Email);
errors.Clear<MyViewModel>(notification: false, vm => vm.Name);

// Factory methods
var created = ErrorCollectionExtensions.Create<MyViewModel>(vm => vm.Name, "Required");

// Combine nullable collections
ErrorCollection? serverErrors = ErrorCollection.Create("Email", "Already in use");
ErrorCollection? modelErrors = null;
var merged = modelErrors.Combine(serverErrors);

// Prefix keys for nested object validation
var addressErrors = ErrorCollection.Create("Street", "Street is required")
    .WithPrefix("Address"); // key becomes "Address.Street"

Note: Lambdas are implemented as extension methods to keep ErrorCollection lean. Performance is comparable to string-based overloads for typical MVVM usage.

Event Notifications

You can subscribe to changes in the error collection:

var errors = new ErrorCollection();
errors.ErrorsChanged += (sender, e) => 
{
    Console.WriteLine($"Property '{e.PropertyName}' was updated.");
};

errors.Add("Username", "Already exists.");
Exception Handling

Easily convert exceptions into error collections:

try 
{
    // Some code that throws
}
catch (Exception ex)
{
    // Default behavior is shallow
    var errors = ErrorCollection.Create(ex);
    
    // Request recursive capture explicitly
    var deepErrors = ErrorCollection.Create(ex, deep: true);

    // OR using extension method
    var errorsExt = ex.ToErrorCollection(deep: true);

    // OR using implicit operator (also shallow by default)
    ErrorCollection errorsImplicit = ex;
}

License

This project is licensed under the terms specified in the LICENSE.txt file.

Benchmarks

The project is highly optimized for performance. The following numbers come from BenchmarkDotNet ShortRun on Apple M5, measuring the current library behavior in .NET 8.0, .NET 9.0, and .NET 10.0:

Method .NET 8.0 .NET 9.0 .NET 10.0 Allocated Memory
AnyErrorProperty 4.40 ns 4.79 ns 3.42 ns 0 B
GetProperties 22.58 ns 22.85 ns 17.52 ns 96 B
GetProperties_ExceptNullFilter 24.46 ns 23.85 ns 18.43 ns 96 B
AddError 35.52 ns 38.15 ns 27.51 ns 312 B
AddMultipleErrors 76.79 ns 61.48 ns 57.39 ns 392-472 B
AddMultipleErrors_WithSanitization 62.20 ns 61.96 ns 60.24 ns 384 B
GetErrors_SingleProperty 55.26 ns 44.06 ns 34.37 ns 232 B
GetErrors 132.85 ns 137.84 ns 114.05 ns 360-832 B
GetErrors_ExceptNullFilter 127.64 ns 136.32 ns 113.87 ns 360-832 B
GetEntries_ExceptNullFilter 169.20 ns 185.23 ns 119.78 ns 872 B
CreateFromException_Shallow 35.52 ns 38.53 ns 25.85 ns 312 B
CreateFromException_Deep 69.23 ns 81.48 ns 55.45 ns 408 B
DeserializeJson_WithSanitization 859.33 ns 785.90 ns 629.56 ns 1912-1944 B

Notes:

  • .NET 10.0 is the fastest runtime in almost every measured scenario.
  • The new defensive behaviors (null filters, sanitization, shallow/deep exception handling) remain in the same performance tier as the previous API surface.
  • AnyError is so fast that with ShortRun it collapses to 0.000 ns; treat it as effectively sub-nanosecond rather than literally zero.

Author

Sebastián Martínez Pérez - Pitasoft, S.L.


Español

Pitasoft.Error es una biblioteca de clases diseñada para facilitar el manejo y la gestión de colecciones de errores en aplicaciones .NET. Permite agrupar errores por propiedad, manejar notificaciones de cambios y serializar/deserializar colecciones de errores a JSON.

Características principales

  • ErrorCollection: Una colección robusta para almacenar errores asociados a nombres de propiedades.
  • Propiedades de Factoría: Accede a ErrorCollection.Empty para obtener una nueva instancia vacía de la colección.
  • IErrorCollectionContainer: Interfaz estándar para objetos (como ViewModels) que exponen una ErrorCollection.
  • Alcance del Paquete NuGet: El paquete NuGet contiene solo la biblioteca de clases (no incluye aplicaciones de ejemplo UI).
  • Alto Rendimiento: Optimizada para minimizar las asignaciones de memoria y maximizar la velocidad, especialmente en operaciones de filtrado y limpieza masiva. Utiliza [MethodImpl(MethodImplOptions.AggressiveInlining)] en rutas críticas y ha sido refactorizada para eliminar la sobrecarga de LINQ en los métodos principales.
  • Filtrado Avanzado: Recuperación flexible de errores mediante operaciones Intersect o Except con JointActionErrors.
  • Soporte INotifyDataErrorInfo: Implementa completamente INotifyDataErrorInfo para una integración fluida con WPF, Avalonia UI, MAUI y otros marcos de trabajo con enlace de datos.
  • Notificaciones: Soporte para eventos cuando la colección de errores cambia.
  • Conteo Total de Errores: La propiedad Count devuelve el número total de mensajes de error individuales en todas las propiedades.
  • Serialización JSON: Extensiones integradas para convertir colecciones de errores a JSON y viceversa.
  • Construcción y Deserialización Más Seguras: Las copias por constructor no comparten listas mutables y la deserialización JSON ignora entradas inválidas (claves vacías o listas nulas/vacías).
  • Métodos de extensión con expresiones lambda: Selección tipada de propiedades mediante Expression<Func<T, object?>> (sin cadenas mágicas).
  • Soporte Multi-target: Compatible con .NET 8.0, .NET 9.0 y .NET 10.0.
  • Fluent API: Métodos encadenables para una configuración rápida y legible de las colecciones de errores.
  • Detección de Errores de Excepción: Extracción automática de mensajes de error de jerarquías de excepciones complejas.

Instalación

Puedes instalar este paquete a través de NuGet:

dotnet add package Pitasoft.Error

Alcance del paquete y compatibilidad UI

  • El paquete NuGet Pitasoft.Error distribuye exclusivamente la biblioteca de manejo de errores.
  • Está diseñado para trabajar con INotifyDataErrorInfo, por lo que es compatible con WPF, MAUI, Avalonia UI y otros frameworks orientados a MVVM.

Novedades / capacidades actuales

  • ErrorCollection.Empty devuelve una instancia nueva en cada acceso (sin estado mutable compartido).
  • Count devuelve el total de mensajes individuales entre todas las propiedades.
  • Clear(...) y ClearAll(...) son las APIs recomendadas (Remove* se mantiene por compatibilidad y está obsoleto).
  • Las extensiones con lambda eliminan cadenas mágicas (Add<T>, AnyError<T>, Clear<T> y Create<T>).
  • La serialización JSON se puede personalizar con SetJsonSerializerOptions(...) para el flujo async actual sin cambiar los valores predeterminados del proceso.
  • Constructores y convertidor JSON normalizan/copían datos para evitar listas compartidas y entradas inválidas.
  • Las entradas null en constructores y APIs bulk se tratan de forma defensiva como vacío/no-op, y los mensajes de error null/vacíos/con espacios se ignoran.
  • Combine(...) fusiona colecciones anulables sin comprobaciones de null adicionales (devuelve null solo si ambas son nulas/vacías).
  • WithPrefix(...) crea una nueva colección con claves prefijadas (útil para objetos anidados como Direccion.Calle).
  • Las APIs basadas en excepciones son superficiales por defecto; usa deep: true cuando quieras incluir recursivamente las excepciones internas.
Nuevos comportamientos añadidos (detallado)
1) Copia segura en constructores

Al crear una colección desde diccionarios/listas existentes, las listas internas se copian para evitar mutaciones compartidas accidentales.

var source = new List<string> { "E1" };
var ec = new ErrorCollection(new[]
{
    new KeyValuePair<string, List<string>>("Nombre", source)
});

source.Add("E2"); // no modifica ec
2) Deserialización JSON defensiva

La deserialización ignora entradas inválidas (claves vacías, listas null, listas vacías) y conserva solo errores válidos.

var json = "{\"Valido\":[\"E1\"],\"Vacio\":[],\"Nulo\":null,\" \":[\"Ignorado\"]}";
var ec = json.ToErrorCollection();

// Solo queda "Valido"
3) Entrada JSON nula/vacía segura

ToErrorCollection devuelve siempre una instancia no nula para entrada null o vacía.

string? raw = null;
var ec = raw.ToErrorCollection(); // colección vacía, nunca null
4) Manejo defensivo de null en APIs bulk

Las operaciones bulk de añadir, limpiar y filtrar tratan null como vacío/no-op en lugar de lanzar.

var ec = new ErrorCollection();

ec.Add("Nombre", (string[]?)null);             // no-op
ec.Clear((IEnumerable<string>?)null);          // no-op
var todos = ec.GetErrors(JointActionErrors.Except, (string[]?)null);
5) Los valores por defecto al tratar excepciones son explícitos

Los métodos de creación y conversión basados en excepciones añaden solo el mensaje de la excepción superior por defecto. Usa deep: true para incluir toda la cadena de excepciones internas.

var superficial = ErrorCollection.Create(ex);            // solo mensaje principal
var profunda = ErrorCollection.Create(ex, deep: true);   // incluye inner exceptions

Uso en backend (Minimal API / Web API)

Pitasoft.Error también es útil en servicios backend para estandarizar validaciones y respuestas de error.

using Microsoft.AspNetCore.Mvc;
using Pitasoft.Error;

var builder = WebApplication.CreateBuilder(args);
var app = builder.Build();

app.MapPost("/usuarios", ([FromBody] CrearUsuarioRequest request) =>
{
    var errors = ErrorCollection.Empty;

    if (string.IsNullOrWhiteSpace(request.Email))
        errors.Add(nameof(request.Email), "El email es obligatorio.");

    if (!request.AceptaTerminos)
        errors.Add(nameof(request.AceptaTerminos), "Debes aceptar los términos.");

    if (errors.HasErrors)
        return Results.ValidationProblem(
            errors.ToDictionary(kv => kv.Key, kv => kv.Value.ToArray()));

    return Results.Created($"/usuarios/{Guid.NewGuid()}", new { ok = true });
});

app.Run();

public record CrearUsuarioRequest(string? Email, bool AceptaTerminos);

Uso básico

Tabla rápida de API
API Propósito Ejemplo
ErrorCollection.Empty Crear una colección vacía nueva var errors = ErrorCollection.Empty;
Add(...) Agregar uno o varios errores errors.Add("Email", "Obligatorio");
Clear(...) / ClearAll() Limpiar errores por propiedad o todos errors.Clear("Email");
AnyError(...) Comprobaciones rápidas de errores errors.AnyError("Email");
GetErrors(...) Obtener mensajes de error errors.GetErrors();
GetProperties(...) Obtener propiedades con errores errors.GetProperties();
FirstOrDefault(...) Obtener el primer error global o por propiedad errors.FirstOrDefault("Email");
ToJson() / ToErrorCollection() Serializar/deserializar errores var json = errors.ToJson();
Add<T>(...) / Clear<T>(...) Helpers tipados con lambda errors.Add<UsuarioVm>(x => x.Email, "Obligatorio");
ErrorCollection.Create(...) Métodos factoría para creación rápida var e = ErrorCollection.Create("General", "Fallo");
Acceso rápido a errores
// Obtener el primer mensaje de error de todas las propiedades (útil para resúmenes en la UI)
string? primerError = errors.FirstOrDefault();

// Comprobar errores en una propiedad específica (comprobación rápida O(1))
bool tieneErroresNombre = errors.AnyError("Nombre");
Crear una colección de errores, añadir y limpiar errores
using Pitasoft.Error;

// Crear mediante constructor o propiedad de factoría
var errors = ErrorCollection.Empty;

// Fluent API: encadenar adiciones de forma fluida
errors.Add("Nombre", "El nombre es obligatorio.")
      .Add("Email", "El formato del email no es válido.");

// Count devuelve el número total de mensajes de error individuales (2 en este caso)
int totalErrores = errors.Count;

// Soporte para múltiples errores a la vez (params string[])
errors.Add("Estado", "Valor inválido", "Fuera de rango");

if (errors.AnyError())
{
    // Manejar errores de forma global
}

// Limpiar errores de una propiedad específica
errors.Clear("Nombre");

// Limpiar todos los errores
errors.ClearAll();

Nota: Los métodos Remove y RemoveAll han sido marcados como obsoletos en favor de Clear y ClearAll para mantener la consistencia con las colecciones estándar de .NET.

Métodos sin notificaciones

Si necesitas añadir errores sin disparar el evento ErrorsChanged:

errors.AddWithoutNotification("PropiedadSilenciosa", "Este error no disparará un evento.");
Integración con Frameworks de UI (MVVM)

ErrorCollection está diseñado para integrarse fácilmente en ViewModels. Puedes implementar IErrorCollectionContainer para proporcionar una forma estándar de exponer errores:

public class MiViewModel : INotifyDataErrorInfo, IErrorCollectionContainer
{
    public ErrorCollection Errors { get; set; } = new();

    public event EventHandler<DataErrorsChangedEventArgs>? ErrorsChanged
    {
        add => Errors.ErrorsChanged += value;
        remove => Errors.ErrorsChanged -= value;
    }

    public System.Collections.IEnumerable GetErrors(string? propertyName) => 
        ((INotifyDataErrorInfo)Errors).GetErrors(propertyName);

    public bool HasErrors => Errors.HasErrors;

    private string _nombre;
    public string Nombre
    {
        get => _nombre;
        set
        {
            _nombre = value;
            ValidarNombre();
        }
    }

    private void ValidarNombre()
    {
        Errors.Clear(nameof(Nombre));
        if (string.IsNullOrWhiteSpace(Nombre))
            Errors.Add(nameof(Nombre), "El nombre es obligatorio");
    }
}
Ejemplo de compatibilidad INotifyDataErrorInfo (WPF / MAUI / Avalonia)

ErrorCollection ya implementa INotifyDataErrorInfo, así que el ViewModel puede delegar directamente:

public sealed class LoginViewModel : INotifyDataErrorInfo
{
    public ErrorCollection Errors { get; } = new();

    public bool HasErrors => Errors.HasErrors;

    public event EventHandler<DataErrorsChangedEventArgs>? ErrorsChanged
    {
        add => Errors.ErrorsChanged += value;
        remove => Errors.ErrorsChanged -= value;
    }

    public System.Collections.IEnumerable GetErrors(string? propertyName) =>
        ((INotifyDataErrorInfo)Errors).GetErrors(propertyName);

    public void ValidarUsuario(string? nombreUsuario)
    {
        Errors.Clear(nameof(NombreUsuario));
        if (string.IsNullOrWhiteSpace(nombreUsuario))
        {
            Errors.Add(nameof(NombreUsuario), "El nombre de usuario es obligatorio.");
        }
    }

    public string? NombreUsuario { get; set; }
}
Filtrado Avanzado

Puedes filtrar errores usando JointActionErrors para incluir o excluir propiedades específicas:

// Obtener errores solo para propiedades específicas
var erroresRelevantes = errors.GetErrors(JointActionErrors.Intersect, "Usuario", "Password");

// Obtener errores para todas las propiedades EXCEPTO las especificadas
var otrosErrores = errors.GetErrors(JointActionErrors.Except, "AceptaTerminos");

// Comprobar si existe algún error para propiedades específicas
bool tieneErroresCriticos = errors.AnyError(JointActionErrors.Intersect, "Sistema", "BaseDeDatos");
Uso de métodos estáticos de creación
var errors = ErrorCollection.Create("General", "Ha ocurrido un error inesperado.");
Serialización a JSON
using Pitasoft.Error.Extensions;

string json = errors.ToJson();
Deserialización desde JSON
string json = "{\"nombre\":[\"Error 1\"]}";
var errors = json.ToErrorCollection();
Extensiones con expresiones lambda

Estas utilidades residen en el espacio de nombres Pitasoft.Error.Extensions.

using Pitasoft.Error;
using Pitasoft.Error.Extensions;

var errors = new ErrorCollection();

// Añadir errores sin cadenas mágicas
errors.Add<MiViewModel>(vm => vm.Nombre, "El nombre es obligatorio");
errors.Add<MiViewModel>(vm => vm.Email, "Formato inválido", "Dominio no permitido");

// Consultar errores
bool tieneErroresEmail = errors.AnyError<MiViewModel>(vm => vm.Email);

// Consultas conjuntas (Intersect/Except)
bool algunCampoUsuarioConErrores = errors.AnyError<MiViewModel>(JointActionErrors.Intersect,
    vm => vm.Nombre, vm => vm.Email);

// Limpiar por expresión de propiedad
errors.Clear<MiViewModel>(vm => vm.Email);
errors.Clear<MiViewModel>(vm => vm.Nombre, vm => vm.Email);
errors.Clear<MiViewModel>(notification: false, vm => vm.Nombre);

// Factorías
var creado = ErrorCollectionExtensions.Create<MiViewModel>(vm => vm.Nombre, "Requerido");

// Combinar colecciones anulables
ErrorCollection? erroresServidor = ErrorCollection.Create("Email", "Ya está en uso");
ErrorCollection? erroresModelo = null;
var combinada = erroresModelo.Combine(erroresServidor);

// Prefijar claves para validación de objetos anidados
var erroresDireccion = ErrorCollection.Create("Calle", "La calle es obligatoria")
    .WithPrefix("Direccion"); // la clave pasa a ser "Direccion.Calle"

Nota: Las lambdas están implementadas como métodos de extensión para mantener ErrorCollection ligera. El rendimiento es comparable a las sobrecargas basadas en cadenas para uso MVVM típico.

Notificaciones de Eventos

Puedes suscribirte a los cambios en la colección de errores:

var errors = new ErrorCollection();
errors.ErrorsChanged += (sender, e) => 
{
    Console.WriteLine($"La propiedad '{e.PropertyName}' ha sido actualizada.");
};

errors.Add("Usuario", "Ya existe.");
Manejo de Excepciones

Convierte fácilmente excepciones en colecciones de errores:

try 
{
    // Código que lanza una excepción
}
catch (Exception ex)
{
    // El comportamiento por defecto es superficial
    var errors = ErrorCollection.Create(ex);
    
    // Si quieres capturar toda la cadena, indícalo explícitamente
    var deepErrors = ErrorCollection.Create(ex, deep: true);

    // O usando el método de extensión
    var errorsExt = ex.ToErrorCollection(deep: true);

    // O usando el operador implícito (también superficial por defecto)
    ErrorCollection errorsImplicit = ex;
}

Licencia

Este proyecto está bajo la licencia especificada en el archivo LICENSE.txt.

Benchmarks

El proyecto está altamente optimizado. Los siguientes números provienen de BenchmarkDotNet ShortRun sobre Apple M5, midiendo el comportamiento actual de la librería en .NET 8.0, .NET 9.0 y .NET 10.0:

Método .NET 8.0 .NET 9.0 .NET 10.0 Memoria Asignada
AnyErrorProperty 4.40 ns 4.79 ns 3.42 ns 0 B
GetProperties 22.58 ns 22.85 ns 17.52 ns 96 B
GetProperties_ExceptNullFilter 24.46 ns 23.85 ns 18.43 ns 96 B
AddError 35.52 ns 38.15 ns 27.51 ns 312 B
AddMultipleErrors 76.79 ns 61.48 ns 57.39 ns 392-472 B
AddMultipleErrors_WithSanitization 62.20 ns 61.96 ns 60.24 ns 384 B
GetErrors_SingleProperty 55.26 ns 44.06 ns 34.37 ns 232 B
GetErrors 132.85 ns 137.84 ns 114.05 ns 360-832 B
GetErrors_ExceptNullFilter 127.64 ns 136.32 ns 113.87 ns 360-832 B
GetEntries_ExceptNullFilter 169.20 ns 185.23 ns 119.78 ns 872 B
CreateFromException_Shallow 35.52 ns 38.53 ns 25.85 ns 312 B
CreateFromException_Deep 69.23 ns 81.48 ns 55.45 ns 408 B
DeserializeJson_WithSanitization 859.33 ns 785.90 ns 629.56 ns 1912-1944 B

Notas:

  • .NET 10.0 es la runtime más rápida en casi todos los escenarios medidos.
  • Los nuevos comportamientos defensivos (null en filtros, saneado, tratamiento superficial/profundo de excepciones) se mantienen en la misma liga de rendimiento que la API previa.
  • AnyError es tan rápido que con ShortRun aparece como 0.000 ns; interprétalo como coste subnanosegundo, no como cero literal.

Autor

Sebastián Martínez Pérez

Product Compatible and additional computed target framework versions.
.NET 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. 
Compatible target framework(s)
Included target framework(s) (in package)
Learn more about Target Frameworks and .NET Standard.
  • net10.0

    • No dependencies.
  • net8.0

    • No dependencies.
  • net9.0

    • No dependencies.

NuGet packages (5)

Showing the top 5 NuGet packages that depend on Pitasoft.Error:

Package Downloads
Pitasoft.Result

.NET library designed to standardize responses from REST services and internal application layers. It provides a robust set of classes to wrap data, status codes, and error collections, facilitating a unified communication contract between APIs, services, and clients.

Pitasoft.Validation

A flexible and high-performance C# validation library designed to validate objects using multiple strategies, including Data Annotations, custom rules, and a fluent API.

Pitasoft.Web

Librerias basicas de aplicaciones web

Pitasoft.AspNetCore

Librerias basicas de aplicaciones web

Pitasoft.Blazor.Validation

Blazor components for data validation in forms.

GitHub repositories

This package is not used by any popular GitHub repositories.

Version Downloads Last Updated
5.3.14 679 3/23/2026
5.3.13 119 3/23/2026
5.3.12 109 3/23/2026
5.3.11 136 3/22/2026
5.3.10 108 3/22/2026
5.3.9 112 3/22/2026
5.3.8 188 3/20/2026
5.3.7 228 3/13/2026
5.3.6 181 3/10/2026
5.3.5 120 3/10/2026
5.3.4 109 3/10/2026
5.3.3 112 3/9/2026
5.3.2 310 3/2/2026
5.3.1 255 2/24/2026
5.2.2 184 2/23/2026
5.2.1 115 2/22/2026
5.1.1 122 2/17/2026
5.0.3 174 2/4/2026
5.0.2 188 1/26/2026
5.0.1 128 1/26/2026
Loading failed