diff --git a/src/Stateless/Graph/GraphStyleBase.cs b/src/Stateless/Graph/GraphStyleBase.cs
index 0b79c5df..881a5d36 100644
--- a/src/Stateless/Graph/GraphStyleBase.cs
+++ b/src/Stateless/Graph/GraphStyleBase.cs
@@ -64,8 +64,11 @@ public abstract class GraphStyleBase
/// Description of all transitions, in the desired format.
public virtual List FormatAllTransitions(List transitions)
{
- List lines = new List();
- if (transitions == null) return lines;
+ if (transitions == null)
+ return new List();
+
+ // Eagerly set the initial capacity to minimize re-allocation of internal array.
+ List lines = new List(transitions.Count);
foreach (var transit in transitions)
{
@@ -84,26 +87,23 @@ public virtual List FormatAllTransitions(List transitions)
stay.SourceState.NodeName, stay.Guards.Select(x => x.Description));
}
}
- else
+ else if (transit is FixedTransition fix)
{
- if (transit is FixedTransition fix)
- {
- line = FormatOneTransition(fix.SourceState.NodeName, fix.Trigger.UnderlyingTrigger.ToString(),
+ line = FormatOneTransition(fix.SourceState.NodeName, fix.Trigger.UnderlyingTrigger.ToString(),
fix.DestinationEntryActions.Select(x => x.Method.Description),
fix.DestinationState.NodeName, fix.Guards.Select(x => x.Description));
- }
- else
- {
- if (transit is DynamicTransition dyn)
- {
- line = FormatOneTransition(dyn.SourceState.NodeName, dyn.Trigger.UnderlyingTrigger.ToString(),
+ }
+ else if (transit is DynamicTransition dyn)
+ {
+ line = FormatOneTransition(dyn.SourceState.NodeName, dyn.Trigger.UnderlyingTrigger.ToString(),
dyn.DestinationEntryActions.Select(x => x.Method.Description),
dyn.DestinationState.NodeName, new List { dyn.Criterion });
- }
- else
- throw new ArgumentException("Unexpected transition type");
- }
}
+ else
+ {
+ throw new ArgumentException("Unexpected transition type");
+ }
+
if (line != null)
lines.Add(line);
}
diff --git a/src/Stateless/ParameterConversion.cs b/src/Stateless/ParameterConversion.cs
index ff28c4f7..631390db 100644
--- a/src/Stateless/ParameterConversion.cs
+++ b/src/Stateless/ParameterConversion.cs
@@ -11,7 +11,7 @@ public static object Unpack(object[] args, Type argType, int index)
if (args.Length == 0)
return null;
- if (args.Length <= index)
+ if (args.Length <= index || index < 0)
throw new ArgumentException(
string.Format(ParameterConversionResources.ArgOfTypeRequiredInPosition, argType, index));
diff --git a/src/Stateless/Reflection/DynamicTransitionInfo.cs b/src/Stateless/Reflection/DynamicTransitionInfo.cs
index a8179256..ca12090a 100644
--- a/src/Stateless/Reflection/DynamicTransitionInfo.cs
+++ b/src/Stateless/Reflection/DynamicTransitionInfo.cs
@@ -1,4 +1,5 @@
-using System.Collections.Generic;
+using System;
+using System.Collections.Generic;
namespace Stateless.Reflection
{
@@ -19,12 +20,12 @@ public DynamicStateInfo(string destinationState, string criterion)
///
/// The name of the destination state
///
- public string DestinationState { get; set; }
+ public string DestinationState { get; }
///
/// The reason this destination state was chosen
///
- public string Criterion { get; set; }
+ public string Criterion { get; }
}
///
@@ -80,15 +81,13 @@ public class DynamicTransitionInfo : TransitionInfo
public static DynamicTransitionInfo Create(TTrigger trigger, IEnumerable guards,
InvocationInfo selector, DynamicStateInfos possibleStates)
{
- var transition = new DynamicTransitionInfo
+ return new DynamicTransitionInfo
{
Trigger = new TriggerInfo(trigger),
- GuardConditionsMethodDescriptions = guards ?? new List(),
+ GuardConditionsMethodDescriptions = guards ?? Array.Empty(),
DestinationStateSelectorDescription = selector,
PossibleDestinationStates = possibleStates // behaviour.PossibleDestinationStates?.Select(x => x.ToString()).ToArray()
};
-
- return transition;
}
private DynamicTransitionInfo() { }
diff --git a/src/Stateless/Reflection/FixedTransitionInfo.cs b/src/Stateless/Reflection/FixedTransitionInfo.cs
index 60275476..acd46896 100644
--- a/src/Stateless/Reflection/FixedTransitionInfo.cs
+++ b/src/Stateless/Reflection/FixedTransitionInfo.cs
@@ -1,4 +1,4 @@
-using System.Collections.Generic;
+using System;
using System.Linq;
namespace Stateless.Reflection
@@ -10,16 +10,14 @@ public class FixedTransitionInfo : TransitionInfo
{
internal static FixedTransitionInfo Create(StateMachine.TriggerBehaviour behaviour, StateInfo destinationStateInfo)
{
- var transition = new FixedTransitionInfo
+ return new FixedTransitionInfo
{
Trigger = new TriggerInfo(behaviour.Trigger),
DestinationState = destinationStateInfo,
GuardConditionsMethodDescriptions = behaviour.Guard == null
- ? new List() : behaviour.Guard.Conditions.Select(c => c.MethodDescription),
+ ? Array.Empty() : behaviour.Guard.Conditions.Select(c => c.MethodDescription),
IsInternalTransition = behaviour is StateMachine.InternalTriggerBehaviour
};
-
- return transition;
}
private FixedTransitionInfo() { }
diff --git a/src/Stateless/Reflection/IgnoredTransitionInfo.cs b/src/Stateless/Reflection/IgnoredTransitionInfo.cs
index 6e669950..47607881 100644
--- a/src/Stateless/Reflection/IgnoredTransitionInfo.cs
+++ b/src/Stateless/Reflection/IgnoredTransitionInfo.cs
@@ -1,4 +1,4 @@
-using System.Collections.Generic;
+using System;
using System.Linq;
namespace Stateless.Reflection
@@ -14,7 +14,7 @@ internal static IgnoredTransitionInfo Create(StateMachine() : behaviour.Guard.Conditions.Select(c => c.MethodDescription)
+ ? Array.Empty() : behaviour.Guard.Conditions.Select(c => c.MethodDescription)
};
return transition;
diff --git a/src/Stateless/Reflection/InvocationInfo.cs b/src/Stateless/Reflection/InvocationInfo.cs
index 736425b6..3dfb62d9 100644
--- a/src/Stateless/Reflection/InvocationInfo.cs
+++ b/src/Stateless/Reflection/InvocationInfo.cs
@@ -7,6 +7,8 @@ namespace Stateless.Reflection
///
public class InvocationInfo
{
+ private static readonly char[] methodNameChars = { '<', '>', '`' };
+
readonly string _description; // _description can be null if user didn't specify a description
///
@@ -65,7 +67,7 @@ public string Description
return _description;
if (MethodName == null)
return SpecialConstants.NullString;
- if (MethodName.IndexOfAny(new char[] { '<', '>', '`' }) >= 0)
+ if (MethodName.IndexOfAny(methodNameChars) >= 0)
return DefaultFunctionDescription;
return MethodName;
}
diff --git a/test/Stateless.Tests/ParameterConversionTests.cs b/test/Stateless.Tests/ParameterConversionTests.cs
new file mode 100644
index 00000000..314f726d
--- /dev/null
+++ b/test/Stateless.Tests/ParameterConversionTests.cs
@@ -0,0 +1,76 @@
+using System;
+using Xunit;
+
+namespace Stateless.Tests
+{
+ public class ParameterConversionTests
+ {
+ private readonly object[] args = { 5, 10, 15 };
+
+ [Fact]
+ public void Unpack_ShouldReturnArg_WhenValidationSucceeds()
+ {
+ Assert.Equal(5, ParameterConversion.Unpack(args, typeof(int), 0));
+ Assert.Equal(10, ParameterConversion.Unpack(args, typeof(int), 1));
+ Assert.Equal(15, ParameterConversion.Unpack(args, typeof(int), 2));
+ Assert.Null(ParameterConversion.Unpack(new object[] { "John", null, "Doe" }, typeof(string), 1));
+ }
+
+ [Fact]
+ public void Unpack_ShouldThrowArgumentNullException_WhenArgsIsNull()
+ {
+ Assert.Throws(() => ParameterConversion.Unpack(null, null, 0));
+ }
+
+ [Fact]
+ public void Unpack_ShouldReturnNull_WhenArgsLengthIsZero()
+ {
+ Assert.Null(ParameterConversion.Unpack(Array.Empty