Skip to content

Commit 77c1d92

Browse files
committed
Refactor state enter/exit actions to improve readability and type safety.
1 parent 5f8bf66 commit 77c1d92

20 files changed

+239
-212
lines changed

Binstate.sln.DotSettings

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -305,6 +305,7 @@
305305
</TypePattern>
306306
&lt;/Patterns&gt;</s:String>
307307
<s:Boolean x:Key="/Default/CodeStyle/Naming/CSharpNaming/ApplyAutoDetectedRules/@EntryValue">False</s:Boolean>
308+
<s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/UserRules/=53eecf85_002Dd821_002D40e8_002Dac97_002Dfdb734542b84/@EntryIndexedValue">&lt;Policy&gt;&lt;Descriptor Staticness="Instance" AccessRightKinds="Protected, ProtectedInternal, Internal, Public, PrivateProtected" Description="Instance fields (not private)"&gt;&lt;ElementKinds&gt;&lt;Kind Name="FIELD" /&gt;&lt;Kind Name="READONLY_FIELD" /&gt;&lt;/ElementKinds&gt;&lt;/Descriptor&gt;&lt;Policy Inspect="True" WarnAboutPrefixesAndSuffixes="False" Prefix="_" Suffix="" Style="aaBb_AaBb" /&gt;&lt;/Policy&gt;</s:String>
308309
<s:Boolean x:Key="/Default/Environment/Filtering/ExcludeCoverageFilters/=Binstate_002ETests_003B_002A_003B_002A_003B_002A/@EntryIndexedValue">True</s:Boolean>
309310
<s:Boolean x:Key="/Default/Environment/SettingsMigration/IsMigratorApplied/=JetBrains_002EReSharper_002EPsi_002ECSharp_002ECodeStyle_002ECSharpKeepExistingMigration/@EntryIndexedValue">True</s:Boolean>
310311
<s:Boolean x:Key="/Default/Environment/SettingsMigration/IsMigratorApplied/=JetBrains_002EReSharper_002EPsi_002ECSharp_002ECodeStyle_002ECSharpPlaceEmbeddedOnSameLineMigration/@EntryIndexedValue">True</s:Boolean>

src/Binstate/src/Builder.ConfiguratorOf.EnterAction.cs

Lines changed: 6 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
using System.Reflection;
33
using System.Runtime.CompilerServices;
44
using System.Threading.Tasks;
5+
using BeatyBit.Bits;
56

67
namespace BeatyBit.Binstate;
78

@@ -13,7 +14,7 @@ internal class EnterAction : ExitAction, IEnterAction
1314
{
1415
private const string AsyncVoidMethodNotSupported = "'async void' methods are not supported, use Task return type for async method";
1516

16-
internal EnterAction(StateConfig stateConfig) : base(stateConfig) { }
17+
internal EnterAction(StateConfig<Unit> stateConfig) : base(stateConfig) { }
1718

1819
public IExitAction OnEnter(Action? enterAction = null)
1920
{
@@ -28,22 +29,23 @@ public IExitAction OnEnter(Action<IStateController<TEvent>> enterAction)
2829
if(enterAction is null) throw new ArgumentNullException(nameof(enterAction));
2930
if(IsAsyncMethod(enterAction.Method)) throw new ArgumentException(AsyncVoidMethodNotSupported);
3031

31-
StateConfig.EnterAction = ConvertToGeneralForm(enterAction);
32+
StateConfig.EnterAction = State<TState, TEvent, Unit>.EnterAction.Create(enterAction);
3233
return this;
3334
}
3435

3536
public IExitAction OnEnter(Func<Task> enterAction)
3637
{
3738
if(enterAction is null) throw new ArgumentNullException(nameof(enterAction));
3839

39-
return OnEnter(_ => enterAction());
40+
StateConfig.EnterAction = State<TState, TEvent, Unit>.EnterAction.Create(enterAction);
41+
return this;
4042
}
4143

4244
public IExitAction OnEnter(Func<IStateController<TEvent>, Task> enterAction)
4345
{
4446
if(enterAction is null) throw new ArgumentNullException(nameof(enterAction));
4547

46-
StateConfig.EnterAction = enterAction;
48+
StateConfig.EnterAction = State<TState, TEvent, Unit>.EnterAction.Create(enterAction);
4749
return this;
4850
}
4951

@@ -112,20 +114,6 @@ public IExitAction OnEnter(Func<IStateController<TEvent>, Task> enterAction)
112114
// }
113115

114116
private static bool IsAsyncMethod(MemberInfo method) => method.GetCustomAttribute(typeof(AsyncStateMachineAttribute)) is not null;
115-
116-
private static Func<IStateController<TEvent>, Task?> ConvertToGeneralForm(Action<IStateController<TEvent>> enterAction)
117-
=> controller =>
118-
{
119-
enterAction(controller);
120-
return null;
121-
};
122-
123-
private static Func<IStateController<TEvent>, TArgument, Task?> ConvertToGeneralForm<TArgument>(Action<IStateController<TEvent>, TArgument> enterAction)
124-
=> (controller, argument) =>
125-
{
126-
enterAction(controller, argument);
127-
return null;
128-
};
129117
}
130118
}
131119
}

src/Binstate/src/Builder.ConfiguratorOf.EnterAction`1.cs

Lines changed: 11 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -13,14 +13,14 @@ internal class EnterAction<TStateArgument> : ExitAction<TStateArgument>, IEnterA
1313
{
1414
private const string AsyncVoidMethodNotSupported = "'async void' methods are not supported, use Task return type for async method";
1515

16-
internal EnterAction(StateConfig stateConfig) : base(stateConfig) { }
16+
internal EnterAction(StateConfig<TStateArgument> stateConfig) : base(stateConfig) { }
1717

1818
public IExitAction<TStateArgument> OnEnter(Action enterAction)
1919
{
2020
if(enterAction is null) throw new ArgumentNullException(nameof(enterAction));
2121
if(IsAsyncMethod(enterAction.Method)) throw new ArgumentException(AsyncVoidMethodNotSupported);
2222

23-
StateConfig.EnterAction = enterAction;
23+
StateConfig.EnterAction = State<TState, TEvent, TStateArgument>.EnterAction.Create(enterAction);
2424
return this;
2525
}
2626

@@ -29,23 +29,23 @@ public IExitAction<TStateArgument> OnEnter(Action<IStateController<TEvent>> ente
2929
if(enterAction is null) throw new ArgumentNullException(nameof(enterAction));
3030
if(IsAsyncMethod(enterAction.Method)) throw new ArgumentException(AsyncVoidMethodNotSupported);
3131

32-
StateConfig.EnterAction = ConvertToGeneralForm(enterAction);
32+
StateConfig.EnterAction = State<TState, TEvent, TStateArgument>.EnterAction.Create(enterAction);
3333
return this;
3434
}
3535

3636
public IExitAction<TStateArgument> OnEnter(Func<Task> enterAction)
3737
{
3838
if(enterAction is null) throw new ArgumentNullException(nameof(enterAction));
3939

40-
StateConfig.EnterAction = enterAction;
40+
StateConfig.EnterAction = State<TState, TEvent, TStateArgument>.EnterAction.Create(enterAction);
4141
return this;
4242
}
4343

4444
public IExitAction<TStateArgument> OnEnter(Func<IStateController<TEvent>, Task> enterAction)
4545
{
4646
if(enterAction is null) throw new ArgumentNullException(nameof(enterAction));
4747

48-
StateConfig.EnterAction = enterAction;
48+
StateConfig.EnterAction = State<TState, TEvent, TStateArgument>.EnterAction.Create(enterAction);
4949
return this;
5050
}
5151

@@ -54,49 +54,36 @@ public IExitAction<TStateArgument> OnEnter(Action<TStateArgument> enterAction)
5454
if(enterAction is null) throw new ArgumentNullException(nameof(enterAction));
5555
if(IsAsyncMethod(enterAction.Method)) throw new ArgumentException(AsyncVoidMethodNotSupported);
5656

57-
return OnEnter((_, argument) => enterAction(argument));
57+
StateConfig.EnterAction = State<TState, TEvent, TStateArgument>.EnterAction.Create(enterAction);
58+
return this;
5859
}
5960

6061
public IExitAction<TStateArgument> OnEnter(Action<IStateController<TEvent>, TStateArgument> enterAction)
6162
{
6263
if(enterAction is null) throw new ArgumentNullException(nameof(enterAction));
6364
if(IsAsyncMethod(enterAction.Method)) throw new ArgumentException(AsyncVoidMethodNotSupported);
6465

65-
StateConfig.EnterAction = ConvertToGeneralForm(enterAction);
66+
StateConfig.EnterAction = State<TState, TEvent, TStateArgument>.EnterAction.Create(enterAction);
6667
return this;
6768
}
6869

6970
public IExitAction<TStateArgument> OnEnter(Func<TStateArgument, Task> enterAction)
7071
{
7172
if(enterAction is null) throw new ArgumentNullException(nameof(enterAction));
7273

73-
return OnEnter((_, argument) => enterAction(argument));
74+
StateConfig.EnterAction = State<TState, TEvent, TStateArgument>.EnterAction.Create(enterAction);
75+
return this;
7476
}
7577

7678
public IExitAction<TStateArgument> OnEnter(Func<IStateController<TEvent>, TStateArgument, Task> enterAction)
7779
{
7880
if(enterAction is null) throw new ArgumentNullException(nameof(enterAction));
7981

80-
StateConfig.EnterAction = enterAction;
82+
StateConfig.EnterAction = State<TState, TEvent, TStateArgument>.EnterAction.Create(enterAction);
8183
return this;
8284
}
8385

8486
private static bool IsAsyncMethod(MemberInfo method) => method.GetCustomAttribute(typeof(AsyncStateMachineAttribute)) is not null;
85-
86-
private static Func<IStateController<TEvent>, Task?> ConvertToGeneralForm(Action<IStateController<TEvent>> enterAction)
87-
=> controller =>
88-
{
89-
enterAction(controller);
90-
return null;
91-
};
92-
93-
private static Func<IStateController<TEvent>, TStateArgument, Task?> ConvertToGeneralForm(
94-
Action<IStateController<TEvent>, TStateArgument> enterAction)
95-
=> (controller, argument) =>
96-
{
97-
enterAction(controller, argument);
98-
return null;
99-
};
10087
}
10188
}
10289
}

src/Binstate/src/Builder.ConfiguratorOf.ExitAction.cs

Lines changed: 11 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
using System;
2+
using BeatyBit.Bits;
23

34
namespace BeatyBit.Binstate;
45

@@ -8,28 +9,31 @@ public static partial class ConfiguratorOf
89
{
910
internal class ExitAction : Transitions, IExitAction
1011
{
11-
protected ExitAction(StateConfig stateConfig) : base(stateConfig) { }
12+
protected ExitAction(StateConfig<Unit> stateConfig) : base(stateConfig) { }
1213

13-
public ITransitions OnExit(Action? exitAction = null)
14+
public ITransitions OnExit(Action exitAction)
1415
{
15-
StateConfig.ExitAction = exitAction ?? throw new ArgumentNullException(nameof(exitAction));
16+
if(exitAction is null) throw new ArgumentNullException(nameof(exitAction));
17+
StateConfig.ExitAction = State<TState, TEvent, Unit>.ExitAction.Create(exitAction);
1618
return this;
1719
}
1820
}
1921

2022
internal class ExitAction<TStateArgument> : Transitions<TStateArgument>, IExitAction<TStateArgument>
2123
{
22-
public ExitAction(StateConfig stateConfig) : base(stateConfig) { }
24+
public ExitAction(StateConfig<TStateArgument> stateConfig) : base(stateConfig) { }
2325

24-
public ITransitions<TStateArgument> OnExit(Action? exitAction = null)
26+
public ITransitions<TStateArgument> OnExit(Action exitAction)
2527
{
26-
StateConfig.ExitAction = exitAction ?? throw new ArgumentNullException(nameof(exitAction));
28+
if(exitAction is null) throw new ArgumentNullException(nameof(exitAction));
29+
StateConfig.ExitAction = State<TState, TEvent, TStateArgument>.ExitAction.Create(exitAction);
2730
return this;
2831
}
2932

3033
public ITransitions<TStateArgument> OnExit(Action<TStateArgument> exitAction)
3134
{
32-
StateConfig.ExitAction = exitAction ?? throw new ArgumentNullException(nameof(exitAction));
35+
if(exitAction is null) throw new ArgumentNullException(nameof(exitAction));
36+
StateConfig.ExitAction = State<TState, TEvent, TStateArgument>.ExitAction.Create(exitAction);
3337
return this;
3438
}
3539
}

src/Binstate/src/Builder.ConfiguratorOf.IExitAction.cs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -17,15 +17,15 @@ public interface IExitAction : ITransitions
1717
/// <param name="exitAction">The action to execute.</param>
1818
/// <returns>An <see cref="ITransitions"/> instance for configuring possible transitions.</returns>
1919
/// <exception cref="ArgumentNullException">Thrown when <paramref name="exitAction"/> is null.</exception>
20-
ITransitions OnExit(Action? exitAction = null);
20+
ITransitions OnExit(Action exitAction);
2121
}
2222

2323

2424
/// <inheritdoc cref="IExitAction"/>
2525
public interface IExitAction<TArgument> : ITransitions<TArgument>
2626
{
2727
/// <inheritdoc cref="IExitAction.OnExit"/>
28-
ITransitions<TArgument> OnExit(Action? exitAction = null);
28+
ITransitions<TArgument> OnExit(Action exitAction);
2929

3030
/// <inheritdoc cref="IExitAction.OnExit"/>
3131
ITransitions<TArgument> OnExit(Action<TArgument> exitAction);

src/Binstate/src/Builder.ConfiguratorOf.State.cs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,7 @@ public interface IState<TStateArgument> : IEnterAction<TStateArgument>
3030

3131
internal class State : EnterAction, IState
3232
{
33-
internal State(StateConfig stateConfig) : base(stateConfig) { }
33+
internal State(StateConfig<Unit> stateConfig) : base(stateConfig) { }
3434

3535
public IEnterAction AsSubstateOf(TState parentStateId)
3636
{
@@ -42,7 +42,7 @@ public IEnterAction AsSubstateOf(TState parentStateId)
4242

4343
internal class State<TStateArgument> : EnterAction<TStateArgument>, IState<TStateArgument>
4444
{
45-
internal State(StateConfig stateConfig) : base(stateConfig) { }
45+
internal State(StateConfig<TStateArgument> stateConfig) : base(stateConfig) { }
4646

4747
public IEnterAction<TStateArgument> AsSubstateOf(TState parentStateId)
4848
{

src/Binstate/src/Builder.ConfiguratorOf.Transitions.cs

Lines changed: 26 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -17,9 +17,9 @@ private static Transition<TStateArgument, TEventArgument>.StateSelector<TState,
1717

1818
internal class Transitions : ITransitions
1919
{
20-
protected readonly StateConfig StateConfig;
20+
protected readonly StateConfig<Unit> StateConfig;
2121

22-
protected Transitions(StateConfig stateConfig) => StateConfig = stateConfig;
22+
protected Transitions(StateConfig<Unit> stateConfig) => StateConfig = stateConfig;
2323

2424
public ITransitions AddTransition(TEvent @event, TState targetState, Transition<Unit, Unit>.Action<TState, TEvent>? action = null)
2525
{
@@ -29,6 +29,8 @@ public ITransitions AddTransition(TEvent @event, TState targetState, Transition<
2929

3030
public ITransitions AddTransition<TEventArgument>(TEvent @event, TState targetState, Transition<Unit, TEventArgument>.Action<TState, TEvent> action)
3131
{
32+
if(action is null) throw new ArgumentNullException(nameof(action));
33+
3234
StateConfig.AddTransition(@event, targetState, null, action);
3335
return this;
3436
}
@@ -39,6 +41,8 @@ public ITransitions AddConditionalTransition<TEventArgument>(
3941
Transition<Unit, TEventArgument>.Guard guard,
4042
Transition<Unit, TEventArgument>.Action<TState, TEvent>? action)
4143
{
44+
if(guard is null) throw new ArgumentNullException(nameof(guard));
45+
4246
StateConfig.AddTransition(@event, targetState, guard, action);
4347
return this;
4448
}
@@ -49,6 +53,8 @@ public ITransitions AddConditionalTransition(
4953
Transition<Unit, Unit>.Guard guard,
5054
Transition<Unit, Unit>.Action<TState, TEvent>? action)
5155
{
56+
if(guard is null) throw new ArgumentNullException(nameof(guard));
57+
5258
StateConfig.AddTransition(@event, targetState, guard, action);
5359
return this;
5460
}
@@ -59,6 +65,8 @@ public ITransitions AddConditionalTransition(
5965
Func<bool> guard,
6066
Transition<Unit, Unit>.Action<TState, TEvent>? action = null)
6167
{
68+
if(guard is null) throw new ArgumentNullException(nameof(guard));
69+
6270
StateConfig.AddTransition(@event, targetState, _ => guard(), action);
6371
return this;
6472
}
@@ -78,6 +86,8 @@ public ITransitions AddDynamicTransition(
7886
Transition<Unit, Unit>.StateSelector<TState, TEvent> selector,
7987
Transition<Unit, Unit>.Action<TState, TEvent>? action = null)
8088
{
89+
if(selector is null) throw new ArgumentNullException(nameof(selector));
90+
8191
StateConfig.AddTransition(@event, selector, action);
8292
return this;
8393
}
@@ -87,12 +97,17 @@ public ITransitions AddDynamicTransition<TEventArgument>(
8797
Transition<Unit, TEventArgument>.StateSelector<TState, TEvent> selector,
8898
Transition<Unit, TEventArgument>.Action<TState, TEvent> action)
8999
{
100+
if(selector is null) throw new ArgumentNullException(nameof(selector));
101+
if(action is null) throw new ArgumentNullException(nameof(action));
102+
90103
StateConfig.AddTransition(@event, selector, action);
91104
return this;
92105
}
93106

94107
public ITransitions AddDynamicTransition(TEvent @event, Func<TState?> selector, Transition<Unit, Unit>.Action<TState, TEvent>? action = null)
95108
{
109+
if(selector is null) throw new ArgumentNullException(nameof(selector));
110+
96111
StateConfig.AddTransition(@event, FuncToSelector<Unit, Unit>(selector), action);
97112
return this;
98113
}
@@ -102,14 +117,22 @@ public ITransitions AddDynamicTransition<TEventArgument>(
102117
Func<TState?> selector,
103118
Transition<Unit, TEventArgument>.Action<TState, TEvent> action)
104119
{
120+
if(selector is null) throw new ArgumentNullException(nameof(selector));
121+
if(action is null) throw new ArgumentNullException(nameof(action));
122+
105123
StateConfig.AddTransition(@event, FuncToSelector<Unit, TEventArgument>(selector), action);
106124
return this;
107125
}
108126

109127
public void AllowReentrancy(TEvent @event, Action? action = null)
110128
=> StateConfig.AddReentrantTransition<Unit>(@event, action is null ? null : _ => action());
111129

112-
public void AllowReentrancy<TEventArgument>(TEvent @event, Action<TEventArgument> action) => StateConfig.AddReentrantTransition(@event, action);
130+
public void AllowReentrancy<TEventArgument>(TEvent @event, Action<TEventArgument> action)
131+
{
132+
if(action is null) throw new ArgumentNullException(nameof(action));
133+
134+
StateConfig.AddReentrantTransition(@event, action);
135+
}
113136
}
114137
}
115138
}

src/Binstate/src/Builder.ConfiguratorOf.Transitions`1.cs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,9 +9,9 @@ public static partial class ConfiguratorOf
99
{
1010
internal class Transitions<TStateArgument> : ITransitions<TStateArgument>
1111
{
12-
protected StateConfig StateConfig { get; }
12+
protected StateConfig<TStateArgument> StateConfig { get; }
1313

14-
public Transitions(StateConfig stateConfig) => StateConfig = stateConfig;
14+
public Transitions(StateConfig<TStateArgument> stateConfig) => StateConfig = stateConfig;
1515

1616
public ITransitions<TStateArgument> AddTransition(
1717
TEvent @event,

0 commit comments

Comments
 (0)