Last active
March 9, 2019 17:11
-
-
Save Virtlink/8722649 to your computer and use it in GitHub Desktop.
Switch on type. The order of the Case() methods is important.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
using System; | |
namespace Virtlink | |
{ | |
/// <summary> | |
/// Executes a particular piece of code based on the type of the argument. | |
/// </summary> | |
/// <example> | |
/// Usage example: | |
/// <code> | |
/// public string GetName(object value) | |
/// { | |
/// string name = null; | |
/// TypeSwitch.On(operand) | |
/// .Case((C x) => name = x.FullName) | |
/// .Case((B x) => name = x.LongName) | |
/// .Case((A x) => name = x.Name) | |
/// .Case((X x) => name = x.ToString(CultureInfo.CurrentCulture)) | |
/// .Case((Y x) => name = x.GetIdentifier()) | |
/// .Default((x) => name = x.ToString()); | |
/// return name; | |
/// } | |
/// </code> | |
/// </example> | |
/// <remarks> | |
/// Created by Virtlink. Original source code on GitHub: | |
/// <see href="https://gist.github.com/Virtlink/8722649"/>. | |
/// </remarks> | |
public static class TypeSwitch | |
{ | |
/// <summary> | |
/// Executes a particular piece of code based on the type of the argument. | |
/// </summary> | |
/// <typeparam name="TSource">The argument's type.</typeparam> | |
/// <param name="value">The switch argument.</param> | |
/// <returns>An object on which the switch cases can be specified.</returns> | |
public static Switch<TSource> On<TSource>(TSource value) | |
{ | |
return new Switch<TSource>(value); | |
} | |
/// <summary> | |
/// Internal class used by the <see cref="TypeSwitch"/> static class. | |
/// </summary> | |
/// <typeparam name="TSource">The source type.</typeparam> | |
public sealed class Switch<TSource> | |
{ | |
/// <summary> | |
/// The source value. | |
/// </summary> | |
private readonly TSource value; | |
/// <summary> | |
/// Whether a switch case handled the value. | |
/// </summary> | |
private bool handled = false; | |
/// <summary> | |
/// Initializes a new instance | |
/// of the <see cref="Switch{TSource}"/> class. | |
/// </summary> | |
/// <param name="value">The switch value.</param> | |
internal Switch(TSource value) | |
{ | |
this.value = value; | |
} | |
/// <summary> | |
/// Executes the specified piece of code when the type | |
/// of the argument is assignable to the specified type. | |
/// </summary> | |
/// <typeparam name="TTarget">The target type.</typeparam> | |
/// <param name="action">The action to execute.</param> | |
/// <returns>An object on which further switch cases | |
/// can be specified.</returns> | |
public Switch<TSource> Case<TTarget>(Action action) | |
where TTarget : TSource | |
{ | |
if (action == null) | |
throw new ArgumentNullException("action"); | |
return Case<TTarget>(_ => action()); | |
} | |
/// <summary> | |
/// Executes the specified piece of code when the type | |
/// of theargument is assignable to the specified type. | |
/// </summary> | |
/// <typeparam name="TTarget">The target type.</typeparam> | |
/// <param name="action">The action to execute.</param> | |
/// <returns>An object on which further switch cases | |
/// can be specified.</returns> | |
public Switch<TSource> Case<TTarget>(Action<TTarget> action) | |
where TTarget : TSource | |
{ | |
if (action == null) | |
throw new ArgumentNullException("action"); | |
if (!this.handled && this.value is TTarget) | |
{ | |
action((TTarget) this.value); | |
this.handled = true; | |
} | |
return this; | |
} | |
/// <summary> | |
/// Executes the specified piece of code when none of the other | |
/// cases handles the specified type. | |
/// </summary> | |
/// <param name="action">The action to execute.</param> | |
public void Default(Action action) | |
{ | |
if (action == null) | |
throw new ArgumentNullException("action"); | |
Default(_ => action()); | |
} | |
/// <summary> | |
/// Executes the specified piece of code when none of the other | |
/// cases handles the specified type. | |
/// </summary> | |
/// <param name="action">The action to execute.</param> | |
public void Default(Action<TSource> action) | |
{ | |
if (action == null) | |
throw new ArgumentNullException("action"); | |
if (!this.handled) | |
action(this.value); | |
} | |
} | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
using NUnit.Framework; | |
namespace Virtlink | |
{ | |
/// <summary> | |
/// Tests for the <see cref="TypeSwitch"/> class. | |
/// </summary> | |
/// <remarks> | |
/// Created by Virtlink. Original source code on GitHub: | |
/// <see href="https://gist.github.com/Virtlink/8722649"/>. | |
/// </remarks> | |
[TestFixture] | |
public class TypeSwitchTests | |
{ | |
interface I { string GetID(); } | |
interface J { string ShortName { get; } } | |
class A { public string Name { get { return "A"; } } } | |
class B : A { public string LongName { get { return "B"; } } } | |
class C : B, I, J { public string FullName { get { return "C"; } } public string GetID() { return "CI"; } public string ShortName { get { return "CJ"; } } } | |
[Test] | |
public void BaseClassObject() | |
{ | |
// Arrange | |
object value = new A(); | |
string name = null; | |
// Act | |
TypeSwitch.On(value) | |
.Case((C x) => name = x.FullName) | |
.Case((B x) => name = x.LongName) | |
.Case<A>(() => name = "A") | |
.Default((x) => name = x.ToString()); | |
// Assert | |
Assert.AreEqual("A", name); | |
} | |
[Test] | |
public void BaseClass() | |
{ | |
// Arrange | |
A value = new A(); | |
string name = null; | |
// Act | |
TypeSwitch.On(value) | |
.Case((C x) => name = x.FullName) | |
.Case((B x) => name = x.LongName) | |
.Case<A>(() => name = "A") | |
.Default((x) => name = x.ToString()); | |
// Assert | |
Assert.AreEqual("A", name); | |
} | |
[Test] | |
public void DerivedClassObject() | |
{ | |
// Arrange | |
object value = new C(); | |
string name = null; | |
// Act | |
TypeSwitch.On(value) | |
.Case((C x) => name = x.FullName) | |
.Case((B x) => name = x.LongName) | |
.Case<A>(() => name = "A") | |
.Default((x) => name = x.ToString()); | |
// Assert | |
Assert.AreEqual("C", name); | |
} | |
[Test] | |
public void DerivedClassAsBaseClass() | |
{ | |
// Arrange | |
A value = new C(); | |
string name = null; | |
// Act | |
TypeSwitch.On(value) | |
.Case((C x) => name = x.FullName) | |
.Case((B x) => name = x.LongName) | |
.Case<A>(() => name = "A") | |
.Default((x) => name = x.ToString()); | |
// Assert | |
Assert.AreEqual("C", name); | |
} | |
[Test] | |
public void DerivedClassCaseWrongOrder() | |
{ | |
// Arrange | |
object value = new C(); | |
string name = null; | |
// Act | |
TypeSwitch.On(value) | |
.Case((B x) => name = x.LongName) | |
.Case((C x) => name = x.FullName) | |
.Case<A>(() => name = "A") | |
.Default((x) => name = x.ToString()); | |
// Assert | |
Assert.AreEqual("B", name); | |
} | |
[Test] | |
public void InterfaceObject() | |
{ | |
// Arrange | |
object value = new C(); | |
string name = null; | |
// Act | |
TypeSwitch.On(value) | |
.Case((I x) => name = x.GetID()) | |
.Case((J x) => name = x.ShortName) | |
.Case((C x) => name = x.FullName) | |
.Case<A>(() => name = "A") | |
.Default((x) => name = x.ToString()); | |
// Assert | |
Assert.AreEqual("CI", name); | |
} | |
[Test] | |
public void Interface() | |
{ | |
// Arrange | |
J value = new C(); | |
string name = null; | |
// Act | |
TypeSwitch.On(value) | |
.Case((J x) => name = x.ShortName) | |
.Case((C x) => name = x.FullName) | |
.Default((x) => name = x.ToString()); | |
// Assert | |
Assert.AreEqual("CJ", name); | |
} | |
[Test] | |
public void Default() | |
{ | |
// Arrange | |
var value = new object(); | |
string name = null; | |
// Act | |
TypeSwitch.On(value) | |
.Case((J x) => name = x.ShortName) | |
.Case((C x) => name = x.FullName) | |
.Default((x) => name = x.ToString()); | |
// Assert | |
Assert.AreEqual("System.Object", name); | |
} | |
[Test] | |
public void DefaultNoArg() | |
{ | |
// Arrange | |
var value = new object(); | |
string name = null; | |
// Act | |
TypeSwitch.On(value) | |
.Case((J x) => name = x.ShortName) | |
.Case((C x) => name = x.FullName) | |
.Default(() => name = "Default"); | |
// Assert | |
Assert.AreEqual("Default", name); | |
} | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Hello, what is the license for this Gist?