DevBitsLab.Feeds 0.0.10

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

DevBitsLab.Feeds

A reactive data management library for .NET that provides state-aware feeds with automatic loading, error handling, and incremental change tracking for collections.

CI NuGet .NET 10 C# 14 License: MIT

📖 Overview

DevBitsLab.Feeds is a reactive data management library that simplifies working with asynchronous data in .NET applications. It provides a unified approach to handling loading states, errors, and data updates through a composable feed abstraction.

Key Benefits

  • Simplified State Management: Automatic tracking of loading, error, and value states
  • Reactive Updates: Event-driven architecture for real-time UI synchronization
  • Incremental Collection Updates: Efficient list change tracking for optimal UI performance
  • Type-Safe Composition: Combine multiple data sources with compile-time type checking
  • MVVM-Ready: Built-in support for ICommand integration and data binding
  • Built-in Analyzers: Roslyn analyzers to enforce best practices

📚 Documentation

📖 Full API Documentation — Detailed guides for all classes and interfaces

Quick Links
Feed<T> Single-value reactive feed
ListFeed<T> Reactive list with change tracking
State<T> Editable state with validation
CombinedFeed Combine multiple feeds
Analyzers Roslyn analyzers reference

📋 Requirements

  • .NET 10.0 or later
  • C# 14 (Preview)
  • Nullable Reference Types enabled

📦 Installation

.NET CLI

dotnet add package DevBitsLab.Feeds

Package Manager Console

Install-Package DevBitsLab.Feeds

🚀 Getting Started

Basic Feed Usage

using DevBitsLab.Feeds;

// Create a feed that loads data from an API
var customerFeed = Feed<Customer>.Create(async ct =>
{
    return await httpClient.GetFromJsonAsync<Customer>($"/api/customers/{id}", ct);
});

// Subscribe to state changes
customerFeed.StateChanged += (sender, e) =>
{
    if (customerFeed.IsLoading)
        ShowLoadingSpinner();
    else if (customerFeed.HasValue)
        DisplayCustomer(customerFeed.Value!);
    else if (customerFeed.HasError)
        ShowError(customerFeed.Error!.Message);
};

// Or await the feed directly
var customer = await customerFeed;

List Feed with Change Tracking

var productsFeed = ListFeed<Product>.Create(async ct =>
{
    return await productApi.GetProductsAsync(ct);
});

// Subscribe to incremental changes
productsFeed.ItemsChanged += (sender, e) =>
{
    switch (e.Change)
    {
        case ListChange<Product>.ItemAdded added:
            InsertRowAt(added.Index, added.Item);
            break;
        case ListChange<Product>.ItemRemoved removed:
            RemoveRowAt(removed.Index);
            break;
    }
};

// Modify the list - changes are tracked automatically
productsFeed.Add(new Product { Name = "New Product" });

Editable State with Validation

var settingsState = State<Settings>.Create(
    loadFunc: async ct => await settingsService.LoadAsync(ct),
    saveFunc: async (settings, ct) => await settingsService.SaveAsync(settings, ct),
    validateFunc: settings =>
    {
        if (string.IsNullOrWhiteSpace(settings?.Username))
            return ValidationResult.Error(nameof(Settings.Username), "Username is required");
        return ValidationResult.Success;
    });

// Edit and save
settingsState.Update(settingsState.Value! with { Theme = "Dark" });

if (settingsState.IsDirty && !settingsState.HasErrors)
    await settingsState.SaveAsync();

🔧 Core Types

Type Description
Feed<T> Reactive feed for single values with async loading
ListFeed<T> Reactive list with incremental change notifications
State<T> Editable wrapper with dirty tracking and validation
ValidationResult Property-level validation errors
CombinedFeed Combines multiple feeds into one

🔗 Standard Interface Support

Interface Type Description
INotifyCollectionChanged ListFeed<T> Native WPF/WinUI ItemsSource binding
INotifyPropertyChanged All feeds Property change notifications
INotifyDataErrorInfo State<T> Validation error binding
IObservable<T> All feeds Rx interop via .ToObservable()
ISupportIncrementalLoading IncrementalListFeed<T>* WinUI virtualized loading

*Available in DevBitsLab.Feeds.WinUI package

📌 Feed States

State Description
None Initial state - no data loaded
Loading Currently fetching data
HasValue Data successfully loaded
HasError Error occurred during loading
Refreshing Updating existing data
Retrying Retrying after error

📚 API Quick Reference

Feed Methods

  • Create(loadFunc) - Create auto-loading feed
  • CreateDeferred(loadFunc) - Create manually-triggered feed
  • FromValue(value) - Create feed with existing value
  • FromError(exception) - Create feed in error state
  • RefreshAsync() - Reload data from source
  • Select(selector) - Transform feed value
  • ToObservable() - Convert to IObservable<T> for Rx

ListFeed Methods

  • Empty() - Create empty list feed
  • FromItems(items) - Create from existing items
  • Add(item) / Insert(index, item) - Add items
  • Remove(item) / RemoveAt(index) - Remove items
  • Update(index, newItem) - Replace item
  • BatchUpdate(action) - Atomic multi-operation
  • ToObservable() - Convert to IObservable<IReadOnlyList<T>>

State Methods

  • Update(value) - Set new value (marks dirty)
  • SaveAsync() - Persist changes
  • RevertAsync() - Discard changes
  • Reset(value) - Set value without dirty flag
  • ToObservable() - Convert to IObservable<T> for Rx

Feed Extension Methods

  • Select(selector) - Project values
  • Filter(predicate) - Filter updates by predicate
  • Debounce(delay) - Debounce value changes
  • DistinctUntilChanged() - Suppress duplicate values
  • Timeout(timeout) - Apply timeout to loading
  • Catch(fallback) - Handle errors with fallback
  • DefaultIfNull(value) - Provide default for null values

🔍 Analyzers

DevBitsLab.Feeds includes Roslyn analyzers to help enforce best practices:

ID Severity Description
FEED001 Warning Missing InitializeBindableFeeds() call in constructor
FEED002 Info Unnecessary InitializeBindableFeeds() call (partial properties don't need it)
FEED003 Warning Feed property should be readonly
FEED004 Info Consider using partial property for automatic lazy binding
FEED005 Warning Missing [BindableFeed] attribute on Feed/State/ListFeed property
FEED006 Info Debounce without UI context may cause threading issues
FEED007 Info Identity Select is unnecessary (.Select(x => x))
FEED008 Warning Filter predicate is constant

See the full analyzer documentation for details.

✅ Best Practices

1. Use [BindableFeed] for Automatic Disposal

The [BindableFeed] attribute automatically implements IDisposable and disposes all marked feeds:

public partial class MyViewModel : ObservableObject
{
    [BindableFeed]
    public partial Feed<Customer> Customer { get; init; }
    
    [BindableFeed]
    public partial State<Settings> Settings { get; init; }
    
    [BindableFeed]
    public partial ListFeed<Order> Orders { get; init; }
    
    // IDisposable is auto-generated! Just call Dispose() when done.
}

// Usage:
var viewModel = new MyViewModel { ... };
// ... use the view model ...
viewModel.Dispose(); // All feeds are automatically disposed!

If you already implement IDisposable, call DisposeBindableFeeds() from your Dispose method:

public partial class MyViewModel : ObservableObject, IDisposable
{
    [BindableFeed]
    public Feed<Customer> Customer { get; } = Feed<Customer>.Create(LoadAsync);
    
    private readonly HttpClient _httpClient = new();
    
    public MyViewModel()
    {
        InitializeBindableFeeds(); // Required for non-partial properties
    }
    
    public void Dispose()
    {
        DisposeBindableFeeds(); // Disposes Customer automatically
        _httpClient.Dispose();  // Your custom cleanup
    }
}

2. Batch Multiple List Changes

// Good: Single notification
listFeed.BatchUpdate(editor =>
{
    editor.Add(item1);
    editor.Add(item2);
});

// Avoid: Multiple notifications
listFeed.Add(item1);
listFeed.Add(item2);

3. Combine Feeds for Derived Data

var dashboardFeed = userFeed.CombineWith(
    ordersFeed,
    (user, orders) => new Dashboard(user, orders));

4. Use Partial Properties for Automatic Binding (C# 13+)

Partial properties enable lazy initialization without needing InitializeBindableFeeds():

public partial class MyViewModel : ObservableObject
{
    [BindableFeed]
    public partial Feed<Person> Person { get; set; }
    
    // No InitializeBindableFeeds() needed - binding is set up lazily on first access
}

📊 Benchmark Results

Benchmarks run nightly. Run configuration: .NET 10, Release mode, Ubuntu Latest.

Feed Throughput

Operation Mean Throughput Allocated
Access Feed.Value 0.002 ns ~500B ops/sec -
Access Feed.HasValue 0.23 ns ~4.4B ops/sec -
Feed.UpdateValue 1.95 ns ~513M ops/sec -
Create deferred Feed 20.19 ns ~50M ops/sec 120 B
Create Feed.FromValue 33.92 ns ~29M ops/sec 208 B
Feed.Select transformation 82.05 ns ~12M ops/sec 360 B

Key Performance Characteristics

  • Zero-allocation reads: Accessing Value, State, IsDirty, HasValue allocates nothing
  • Sub-nanosecond property access: Core read operations complete in under 1 ns
  • Efficient updates: Single-item operations complete in nanoseconds
  • Scalable collections: ListFeed<T> operations scale well with collection size

🤝 Contributing

Contributions are welcome! Please feel free to submit issues and pull requests.

  1. Clone the repository
  2. Open in Visual Studio 2022 or later
  3. Build the solution
  4. Run tests

📄 License

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

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 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 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.

NuGet packages (1)

Showing the top 1 NuGet packages that depend on DevBitsLab.Feeds:

Package Downloads
DevBitsLab.Feeds.WinUI

WinUI extensions for DevBitsLab.Feeds including FeedPresenter control for automatic feed state presentation and ISupportIncrementalLoading support for virtualized list loading.

GitHub repositories

This package is not used by any popular GitHub repositories.

Version Downloads Last Updated
0.0.10 103 1/19/2026
0.0.8 91 1/19/2026
0.0.7 91 1/19/2026
0.0.6 99 1/18/2026
0.0.5 93 1/14/2026

Initial release of DevBitsLab.Feeds with Feed<T>, ListFeed<T>, State<T>, and CombinedFeed support. Supports .NET 8 and 10.