-
-
Notifications
You must be signed in to change notification settings - Fork 173
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Added support for switch style methods. #48
Conversation
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.
I also like the fluent syntax but be aware of the performance implications in this case. Each call to |
src/SmartEnum/Core/DoNotExecute.cs
Outdated
@@ -0,0 +1,21 @@ | |||
using System; | |||
|
|||
namespace Ardalis.SmartEnum.Core |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The namespace can be simply Ardalis.SmartEnum
. This saves the users to have to add using
clauses or Core
for all interface uses.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I put these classes into a "Core" namespace as they're not intended to be used by users. So when a user does using Ardalis.SmartEnum
they don't get these somewhat cryptic implementation details in intellisense.
I'm happy to move them to Ardalis.SmartEnum
if that's where you'd prefer them.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I didn't think of that. That's a good idea to keep Core
.
src/SmartEnum/Core/Execute.cs
Outdated
@@ -0,0 +1,22 @@ | |||
using System; | |||
|
|||
namespace Ardalis.SmartEnum.Core |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The namespace can be simply Ardalis.SmartEnum
. This saves the users to have to add using
clauses or Core
for all interface uses.
src/SmartEnum/Core/IThen.cs
Outdated
@@ -0,0 +1,11 @@ | |||
using System; | |||
|
|||
namespace Ardalis.SmartEnum.Core |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The namespace can be simply Ardalis.SmartEnum
. This saves the users to have to add using
clauses or Core
for all interface uses.
The entities Changing them to One possible solution is to delete public readonly struct Execute<TEnum, TValue>
where TEnum : SmartEnum<TEnum, TValue>
where TValue : IEquatable<TValue>, IComparable<TValue>
{
private readonly bool enabled;
private readonly SmartEnum<TEnum, TValue> smartEnum;
public Execute(bool enabled, SmartEnum<TEnum, TValue> smartEnum)
{
this.enabled = enabled;
this.smartEnum = smartEnum;
}
public SmartEnum<TEnum, TValue> Then(Action doThis)
{
if (enabled)
doThis();
return smartEnum;
}
} And change public Execute<TEnum, TValue> When(params SmartEnum<TEnum, TValue>[] smartEnums) =>
new Execute<TEnum, TValue>(smartEnums.Contains(this), this);
public Execute<TEnum, TValue> When(IEnumerable<SmartEnum<TEnum, TValue>> smartEnums) =>
new Execute<TEnum, TValue>(smartEnums.Contains(this), this); |
Yes, I'd not particularly paid attention to performance, but these are all valid concerns. I will restructure the code to work with structs and removing heap allocations. |
Ok. Let me know what you think of the changes I've made. I've gotten rid of the interface and introduced a I've also added in the I've additionally added in At the moment I've left those two structs inside the ".Core" namespace, so that they doesn't appear in intellisense for users. I could achieve the same thing using an I've also added in a couple of tests around the |
Obviously what I really really want is for this to be "exhaustive" as well... I wonder if we could add another method called e.g.
Clearly this is very different behaviour to compile time exhaustive checking. |
Great! I'll have to check that later. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The changes you made are great! There's just a few more suggestions inline.
Please also add the following to the SwitchBenchmarks.cs
file in the SmartEnum.Benchmarks
project:
[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;
}
This lets you compare performance between this and the other methods of "switching" a SmartEnum
:
Method | Mean | Error | StdDev | Median | Library | Gen 0 | Gen 1 | Gen 2 | Allocated |
---|---|---|---|---|---|---|---|---|---|
Enum_Switch | 0.0056 ns | 0.0100 ns | 0.0252 ns | 0.0000 ns | Enum | - | - | - | - |
Enum_IfEqualsMethodCascade | 251.5596 ns | 4.7387 ns | 4.6540 ns | 250.9543 ns | Enum | 0.2742 | - | - | 432 B |
Enum_IfEqualOperatorCascade | 0.0023 ns | 0.0080 ns | 0.0071 ns | 0.0000 ns | Enum | - | - | - | - |
SmartEnum_Switch | 11.5462 ns | 0.2117 ns | 0.1980 ns | 11.4451 ns | SmartEnum | - | - | - | - |
SmartEnum_SwitchPatternMatching | 11.1354 ns | 0.2059 ns | 0.1607 ns | 11.1402 ns | SmartEnum | - | - | - | - |
SmartEnum_IfCascade | 49.4139 ns | 0.6313 ns | 0.5906 ns | 49.3628 ns | SmartEnum | - | - | - | - |
SmartEnum_When | 283.8668 ns | 6.4530 ns | 6.0361 ns | 282.6747 ns | SmartEnum | 0.4220 | - | - | 664 B |
Notice that the fluent syntax is still a lot slower (283 ns vs. 11 ns) but still a good enough feature to be added.
The heap memory allocations (664 B) are due to the variable capture by the lambda expressions. If you change these to not access variables outside of its own scope, the allocations get down to zero and the performance increases considerably.
Yes. It is a shame about the performance. |
Change access level of When/Then constructors. Change comparison from "==" to ".Equals()" to avoid null check.
I've udpated with the latest suggestions. Added When/Then benchmark method. |
@ardalis IMHO This is a nice feature to add. It needs your seal of approval. 😉 |
Agreed, you two are both awesome, thank you! |
Can you update the README with some instructions for usage (like what you have in the PR description) as part of this PR? |
Because of the limitations of pattern matching SmartEnum also provides a fluent interface to help create clean code: 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 code for your use case. |
Sounds fine; small note with link to benchmarks is perfect. |
README.md updated |
First of all, thank you for this project I was about to set about implementing my own type-safe enum pattern, when I found this project and see that it implements everything I was going to and more and in a very refined way.
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.
I guess I should have opened an issue for this first, but I think code speaks well for developers. I'm happy if this doesn't get merged, but I think it might be a good discussion point.
Also, if you don't think this is suitable, I think it could be implemented as extension methods and go into a "contrib" namespace if you think it has some legs, but don't want it in the project.
I'm providing this snippet to demonstrate how I imagine it being used: