Skip to content

Commit

Permalink
Added support for switch style methods. (#48)
Browse files Browse the repository at this point in the history
* Added support for switch style methods.

I'm not entirely happy with the pattern matching switch statements available in C#. Therefore I'm proposing a fluent interface solution to the same task using a .When().Then() fluent interface in place of a regular switch/case statement.

* Refactor when/then implementation to reduce heap allocations.

* Temporarily move When and Then structs into Core namespace.

* Fix up paramete name in documentation.

* Added When/Then benchmark method.
Change access level of When/Then constructors.
Change comparison from "==" to ".Equals()" to avoid null check.

* Added notes for the When/Then fluent interface
  • Loading branch information
sgoodgrove authored and ardalis committed Jul 2, 2019
1 parent be33416 commit d88f560
Show file tree
Hide file tree
Showing 6 changed files with 290 additions and 1 deletion.
11 changes: 11 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -299,6 +299,17 @@ switch(testEnumVar)
break;
}
```
Because of the limitations of pattern matching SmartEnum also provides a fluent interface to help create clean code:

```csharp
testEnumVar
.When(TestEnum.One).Then(() => ... )
.When(TestEnum.Two).Then(() => ... )
.When(TestEnum.Three).Then(() => ... )
.Default( ... );
```

N.B. For performance critical code the fluent interface carries some overhead that you may wish to avoid. See the available [benchmarks](src/SmartEnum.Benchmarks) code for your use case.

### Persisting with EF Core 2.1 or higher

Expand Down
23 changes: 22 additions & 1 deletion src/SmartEnum.Benchmarks/SwitchBenchmarks.cs
Original file line number Diff line number Diff line change
Expand Up @@ -177,6 +177,27 @@ public TestSmartEnum SmartEnum_IfCascade()
if (value.Equals(TestSmartEnum.Ten))
return TestSmartEnum.Ten;
throw new Exception();
}
}

[Benchmark]
public TestSmartEnum SmartEnum_When()
{
TestSmartEnum result = null;

TestSmartEnum.Ten
.When(TestSmartEnum.One).Then(() => result = TestSmartEnum.One)
.When(TestSmartEnum.Two).Then(() => result = TestSmartEnum.Two)
.When(TestSmartEnum.Three).Then(() => result = TestSmartEnum.Three)
.When(TestSmartEnum.Four).Then(() => result = TestSmartEnum.Four)
.When(TestSmartEnum.Five).Then(() => result = TestSmartEnum.Five)
.When(TestSmartEnum.Six).Then(() => result = TestSmartEnum.Six)
.When(TestSmartEnum.Seven).Then(() => result = TestSmartEnum.Seven)
.When(TestSmartEnum.Eight).Then(() => result = TestSmartEnum.Eight)
.When(TestSmartEnum.Nine).Then(() => result = TestSmartEnum.Nine)
.When(TestSmartEnum.Ten).Then(() => result = TestSmartEnum.Ten)
.Default(() => throw new Exception());

return result;
}
}
}
138 changes: 138 additions & 0 deletions src/SmartEnum.UnitTests/SmartEnumWhenThen.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,138 @@
namespace Ardalis.SmartEnum.UnitTests
{
using FluentAssertions;
using System.Collections.Generic;
using Xunit;

public class SmartEnumWhenThen
{
public static TheoryData<TestEnum> NameData =>
new TheoryData<TestEnum>
{
TestEnum.One,
TestEnum.Two,
TestEnum.Three,
};

[Fact]
public void DefaultConditionDoesNotRunWhenConditionMet()
{
var one = TestEnum.One;

var firstActionRun = false;
var defaultActionRun = false;

one
.When(TestEnum.One).Then(() => firstActionRun = true)
.Default(() => defaultActionRun = true);

firstActionRun.Should().BeTrue();
defaultActionRun.Should().BeFalse();
}

[Fact]
public void DefaultConditionRunsWhenNoConditionMet()
{
var three = TestEnum.Three;

var firstActionRun = false;
var secondActionRun = false;
var defaultActionRun = false;

three
.When(TestEnum.One).Then(() => firstActionRun = true)
.When(TestEnum.Two).Then(() => secondActionRun = true)
.Default(() => defaultActionRun = true);

firstActionRun.Should().BeFalse();
secondActionRun.Should().BeFalse();
defaultActionRun.Should().BeTrue();
}

[Fact]
public void WhenFirstConditionMetFirstActionRuns()
{
var one = TestEnum.One;

var firstActionRun = false;
var secondActionRun = false;

one
.When(TestEnum.One).Then(() => firstActionRun = true)
.When(TestEnum.Two).Then(() => secondActionRun = true);

firstActionRun.Should().BeTrue();
secondActionRun.Should().BeFalse();
}

[Fact]
public void WhenFirstConditionMetSubsequentActionsNotRun()
{
var one = TestEnum.One;

var firstActionRun = false;
var secondActionRun = false;

one
.When(TestEnum.One).Then(() => firstActionRun = true)
.When(TestEnum.One).Then(() => secondActionRun = true);

firstActionRun.Should().BeTrue();
secondActionRun.Should().BeFalse();
}

[Fact]
public void WhenMatchesLastListActionRuns()
{
var three = TestEnum.Three;

var firstActionRun = false;
var secondActionRun = false;
var thirdActionRun = false;

three
.When(TestEnum.One).Then(() => firstActionRun = true)
.When(TestEnum.Two).Then(() => secondActionRun = true)
.When(new List<TestEnum> { TestEnum.One, TestEnum.Two, TestEnum.Three }).Then(() => thirdActionRun = true);

firstActionRun.Should().BeFalse();
secondActionRun.Should().BeFalse();
thirdActionRun.Should().BeTrue();
}

[Fact]
public void WhenMatchesLastParameterActionRuns()
{
var three = TestEnum.Three;

var firstActionRun = false;
var secondActionRun = false;
var thirdActionRun = false;

three
.When(TestEnum.One).Then(() => firstActionRun = true)
.When(TestEnum.Two).Then(() => secondActionRun = true)
.When(TestEnum.One, TestEnum.Two, TestEnum.Three).Then(() => thirdActionRun = true);

firstActionRun.Should().BeFalse();
secondActionRun.Should().BeFalse();
thirdActionRun.Should().BeTrue();
}

[Fact]
public void WhenSecondConditionMetSecondActionRuns()
{
var two = TestEnum.Two;

var firstActionRun = false;
var secondActionRun = false;

two
.When(TestEnum.One).Then(() => firstActionRun = true)
.When(TestEnum.Two).Then(() => secondActionRun = true);

firstActionRun.Should().BeFalse();
secondActionRun.Should().BeTrue();
}
}
}
33 changes: 33 additions & 0 deletions src/SmartEnum/Core/SmartEnumThen.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
using System;

namespace Ardalis.SmartEnum.Core
{
public readonly struct SmartEnumThen<TEnum, TValue>
where TEnum : SmartEnum<TEnum, TValue>
where TValue : IEquatable<TValue>, IComparable<TValue>
{
private readonly bool isMatch;
private readonly SmartEnum<TEnum, TValue> smartEnum;
private readonly bool stopEvaluating;

internal SmartEnumThen(bool isMatch, bool stopEvaluating, SmartEnum<TEnum, TValue> smartEnum)
{
this.isMatch = isMatch;
this.smartEnum = smartEnum;
this.stopEvaluating = stopEvaluating;
}

/// <summary>
/// Calls <paramref name="doThis"/> Action when the preceding When call matches.
/// </summary>
/// <param name="doThis">Action method to call.</param>
/// <returns>A chainable instance of CaseWhen for more when calls.</returns>
public SmartEnumWhen<TEnum, TValue> Then(Action doThis)
{
if (!stopEvaluating && isMatch)
doThis();

return new SmartEnumWhen<TEnum, TValue>(stopEvaluating || isMatch, smartEnum);
}
}
}
58 changes: 58 additions & 0 deletions src/SmartEnum/Core/SmartEnumWhen.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
using System;
using System.Collections.Generic;
using System.Linq;

namespace Ardalis.SmartEnum.Core
{
public readonly struct SmartEnumWhen<TEnum, TValue>
where TEnum : SmartEnum<TEnum, TValue>
where TValue : IEquatable<TValue>, IComparable<TValue>
{
private readonly SmartEnum<TEnum, TValue> smartEnum;
private readonly bool stopEvaluating;

internal SmartEnumWhen(bool stopEvaluating, SmartEnum<TEnum, TValue> smartEnum)
{
this.stopEvaluating = stopEvaluating;
this.smartEnum = smartEnum;
}

/// <summary>
/// Execute this action if no other calls to When have matched.
/// </summary>
/// <param name="action">The Action to call.</param>
public void Default(Action action)
{
if (!stopEvaluating)
{
action();
}
}

/// <summary>
/// When this instance is one of the specified <see cref="SmartEnum{TEnum, TValue}"/> parameters.
/// Execute the action in the subsequent call to Then().
/// </summary>
/// <param name="smartEnumWhen">A collection of <see cref="SmartEnum{TEnum, TValue}"/> values to compare to this instance.</param>
/// <returns>A executor object to execute a supplied action.</returns>
public SmartEnumThen<TEnum, TValue> When(SmartEnum<TEnum, TValue> smartEnumWhen) =>
new SmartEnumThen<TEnum, TValue>(isMatch: smartEnum.Equals(smartEnumWhen), stopEvaluating: stopEvaluating, smartEnum: smartEnum);

/// <summary>
/// When this instance is one of the specified <see cref="SmartEnum{TEnum, TValue}"/> parameters.
/// Execute the action in the subsequent call to Then().
/// </summary>
/// <param name="smartEnums">A collection of <see cref="SmartEnum{TEnum, TValue}"/> values to compare to this instance.</param>
/// <returns>A executor object to execute a supplied action.</returns>
public SmartEnumThen<TEnum, TValue> When(params SmartEnum<TEnum, TValue>[] smartEnums) =>
new SmartEnumThen<TEnum, TValue>(isMatch: smartEnums.Contains(smartEnum), stopEvaluating: stopEvaluating, smartEnum: smartEnum);

/// When this instance is one of the specified <see cref="SmartEnum{TEnum, TValue}"/> parameters.
/// Execute the action in the subsequent call to Then().
/// </summary>
/// <param name="smartEnums">A collection of <see cref="SmartEnum{TEnum, TValue}"/> values to compare to this instance.</param>
/// <returns>A executor object to execute a supplied action.</returns>
public SmartEnumThen<TEnum, TValue> When(IEnumerable<SmartEnum<TEnum, TValue>> smartEnums) =>
new SmartEnumThen<TEnum, TValue>(isMatch: smartEnums.Contains(smartEnum), stopEvaluating: stopEvaluating, smartEnum: smartEnum);
}
}
28 changes: 28 additions & 0 deletions src/SmartEnum/SmartEnum.cs
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@
using System.Reflection;
using System.Runtime.CompilerServices;

using Ardalis.SmartEnum.Core;

/// <summary>
/// A base type to use for creating smart enums with inner value of type <see cref="System.Int32"/>.
/// </summary>
Expand Down Expand Up @@ -287,6 +289,32 @@ public virtual bool Equals(SmartEnum<TEnum, TValue> other)
return _value.Equals(other._value);
}

/// <summary>
/// When this instance is one of the specified <see cref="SmartEnum{TEnum, TValue}"/> parameters.
/// Execute the action in the subsequent call to Then().
/// </summary>
/// <param name="smartEnumWhen">A collection of <see cref="SmartEnum{TEnum, TValue}"/> values to compare to this instance.</param>
/// <returns>A executor object to execute a supplied action.</returns>
public SmartEnumThen<TEnum, TValue> When(SmartEnum<TEnum, TValue> smartEnumWhen) =>
new SmartEnumThen<TEnum, TValue>(this.Equals(smartEnumWhen), false, this);

/// <summary>
/// When this instance is one of the specified <see cref="SmartEnum{TEnum, TValue}"/> parameters.
/// Execute the action in the subsequent call to Then().
/// </summary>
/// <param name="smartEnums">A collection of <see cref="SmartEnum{TEnum, TValue}"/> values to compare to this instance.</param>
/// <returns>A executor object to execute a supplied action.</returns>
public SmartEnumThen<TEnum, TValue> When(params SmartEnum<TEnum, TValue>[] smartEnums) =>
new SmartEnumThen<TEnum, TValue>(smartEnums.Contains(this), false, this);

/// When this instance is one of the specified <see cref="SmartEnum{TEnum, TValue}"/> parameters.
/// Execute the action in the subsequent call to Then().
/// </summary>
/// <param name="smartEnums">A collection of <see cref="SmartEnum{TEnum, TValue}"/> values to compare to this instance.</param>
/// <returns>A executor object to execute a supplied action.</returns>
public SmartEnumThen<TEnum, TValue> When(IEnumerable<SmartEnum<TEnum, TValue>> smartEnums) =>
new SmartEnumThen<TEnum, TValue>(smartEnums.Contains(this), false, this);

public static bool operator ==(SmartEnum<TEnum, TValue> left, SmartEnum<TEnum, TValue> right)
{
// Handle null on left side
Expand Down

0 comments on commit d88f560

Please sign in to comment.