T.Molodoy.MviCore.Net
0.0.1
dotnet add package T.Molodoy.MviCore.Net --version 0.0.1
NuGet\Install-Package T.Molodoy.MviCore.Net -Version 0.0.1
<PackageReference Include="T.Molodoy.MviCore.Net" Version="0.0.1" />
<PackageVersion Include="T.Molodoy.MviCore.Net" Version="0.0.1" />
<PackageReference Include="T.Molodoy.MviCore.Net" />
paket add T.Molodoy.MviCore.Net --version 0.0.1
#r "nuget: T.Molodoy.MviCore.Net, 0.0.1"
#:package T.Molodoy.MviCore.Net@0.0.1
#addin nuget:?package=T.Molodoy.MviCore.Net&version=0.0.1
#tool nuget:?package=T.Molodoy.MviCore.Net&version=0.0.1
T.Molodoy.MviCore.Net
Упрощённый перенос ядра Badoo MVICore на C# и
R3. Библиотека на netstandard2.1, зависимость — R3 1.3.1.
Установка
dotnet add package T.Molodoy.MviCore.Net --version 0.0.1
Быстрый старт
using MviCore.Net;
using SharpExtensions;
using R3;
using var counter = new ReducerFeature<int, int>(
initialState: 0,
reducer: (state, delta) => state + delta);
using var subscription = counter.Subscribe(Console.WriteLine);
counter.Accept(1);
counter.Accept(2);
Console.WriteLine(counter.State);
Feature работает сразу после создания, даже без подписчиков. Новый подписчик получает
текущее состояние, затем результат каждой редукции, включая равные предыдущему состояния.
States позволяет подписываться через IStore / IFeature. Сам feature также является
R3 Observable<TState> и поддерживает обычные операторы R3.
API
| Тип | Назначение |
|---|---|
ReducerFeature<Wish, State, News> |
Wish сразу передаётся в Reducer как Effect |
ActorReducerFeature<Wish, Effect, State, News> |
Actor преобразует Wish в поток Effect |
BaseFeature<Wish, Action, Effect, State, News> |
Полная цепочка с внутренними Action и PostProcessor |
IStore<Wish, State> |
Accept(wish), State, States |
IFeature<Wish, State, News> |
Store плюс News, IsDisposed, Dispose() |
Все три класса имеют вариант без последнего параметра News; в нём используется Nothing.
Компоненты задаются делегатами, поэтому можно передавать лямбды и методы:
WishToAction: Wish → Action
Bootstrapper: () → Observable<Action>
Actor: (State, Action) → Observable<Effect>
Reducer: (State, Effect) → State
PostProcessor:(Action, Effect, State) → Optional<Action>
NewsPublisher:(Action, Effect, State) → Optional<News>
Обязательные параметры конструктора идут первыми; необязательные bootstrapper,
postProcessor и newsPublisher удобно передавать по имени. Сохранены основные имена,
порядок generic-параметров и модель исполнения оригинала. Это не двоично совместимый порт.
Binder, middleware, time travel и платформенные интеграции в это ядро не входят.
Actor и новости
using var feature = new ActorReducerFeature<int, int, int, string>(
initialState: 0,
actor: (state, wish) => Observable.Return(wish * 2),
reducer: (state, effect) => state + effect,
newsPublisher: (wish, effect, state) => state >= 10
? $"Достигнуто {state}"
: default(Optional<string>));
using var states = feature.Subscribe(Console.WriteLine);
using var news = feature.News.Subscribe(Console.WriteLine);
feature.Accept(5);
Actor получает состояние на момент поступления Action. Каждый его Effect применяется
к последнему состоянию, в том числе если другие Actor уже успели его изменить.
Потоки разных Actor могут существовать одновременно; новый Wish не отменяет предыдущие.
Новости получают только текущие подписчики, без сохранения и повторной выдачи.
У ReducerFeature сокращённый SimpleNewsPublisher принимает только (wish, state).
default(Optional<T>) (или default) означает отсутствие Action/News. Само значение неявно
преобразуется в Optional<T>; null для ссылочного типа также означает отсутствие.
Optional<T> предоставляется зависимостью T.Molodoy.SharpExtensions. Это позволяет использовать int, enum и
struct: 0 и false являются полноценными событиями, а не признаком отсутствия результата.
В лямбдах для отсутствия результата предпочтителен явный default(Optional<T>).
Внутренние действия и запуск
using var feature = new BaseFeature<string, int, int, int>(
initialState: 0,
wishToAction: int.Parse,
actor: (_, action) => Observable.Return(action),
reducer: (state, effect) => state + effect,
bootstrapper: () => Observable.Return(1),
postProcessor: (_, _, state) => state == 2 ? 10 : default(Optional<int>));
Console.WriteLine(feature.State);
feature.Accept("1");
Bootstrapper вызывается один раз в конструкторе и может возвращать длительный поток. Синхронный запуск происходит до возврата из конструктора: поля производного класса, которые инициализируются позже, ещё недоступны, а начальные новости не буферизуются. Для отложенного запуска можно вернуть внешний Subject с действиями и отправить событие после создания feature и подписки на выходы.
После Reducer новое состояние сохраняется и публикуется; затем вызывается PostProcessor,
затем NewsPublisher. Оба получают именно состояние этой редукции. Вызовы синхронны:
внутреннее действие из PostProcessor выполняется до публикации новости исходного действия.
Повторный Accept из подписчика тоже выполняется сразу. Очереди нет; бесконечные или
очень глубокие цепочки внутренних действий нужно исключить в бизнес-логике.
Потоки, ошибки и освобождение ресурсов
Все Accept, уведомления Actor/Bootstrapper, подписки и Dispose должны выполняться
в одном потоке. Собственных lock, проверок thread ID, scheduler и синхронизации нет.
Внутри самих примитивов R3 может использоваться синхронизация. Для асинхронных операций
нужно самостоятельно вернуть уведомления в поток feature, например через
ObserveOn(context), где context — нужный SynchronizationContext, перед возвратом потока из Actor.
Ошибки уведомлений следуют модели R3: OnErrorResume, неуспешное завершение Actor или
Bootstrapper и исключения Reducer/PostProcessor/NewsPublisher передаются глобальному
ObservableSystem.RegisterUnhandledExceptionHandler. OnErrorResume не отменяет поток;
неуспешное завершение отменяет только конкретную подписку. Feature остаётся работоспособным.
Исключения WishToAction и фабрики Actor при прямом Accept выходят вызывающему коду.
Ошибки, являющиеся частью бизнес-логики, следует преобразовывать в Effect внутри Actor.
Dispose() отменяет все активные Actor и Bootstrapper, завершает States и News и
допускает повторный вызов. После него Accept бросает ObjectDisposedException, а State
сохраняет последнее значение. Подписки, завершившиеся раньше, сразу удаляются из учёта.
Отписка отдельного наблюдателя не останавливает feature. Если освобождение upstream
бросает исключение, остальные ресурсы всё равно освобождаются, затем выбрасывается
AggregateException.
Проверка
Для тестов нужен .NET SDK 9 или новее:
dotnet test MviCore.Net.sln --configuration Release
dotnet pack MviCore.Net.R3/MviCore.Net.R3.csproj --configuration Release
Тесты проверяют актуальность состояния при чередовании Effect, порядок PostProcessor/News, bootstrap, повторный вход, обработку ошибок, освобождение подписок и отсутствие удержания Action после синхронного завершения Actor.
| Product | Versions 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 | netcoreapp3.0 was computed. netcoreapp3.1 was computed. |
| .NET Standard | netstandard2.1 is compatible. |
| MonoAndroid | monoandroid was computed. |
| MonoMac | monomac was computed. |
| MonoTouch | monotouch was computed. |
| Tizen | tizen60 was computed. |
| Xamarin.iOS | xamarinios was computed. |
| Xamarin.Mac | xamarinmac was computed. |
| Xamarin.TVOS | xamarintvos was computed. |
| Xamarin.WatchOS | xamarinwatchos was computed. |
-
.NETStandard 2.1
- R3 (>= 1.3.1)
- T.Molodoy.SharpExtensions (>= 0.0.48)
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.1 | 43 | 11/8/2026 |