Skip to content

Instantly share code, notes, and snippets.

@Virtlink
Last active March 9, 2019 17:11
Show Gist options
  • Save Virtlink/8722649 to your computer and use it in GitHub Desktop.
Save Virtlink/8722649 to your computer and use it in GitHub Desktop.
Switch on type. The order of the Case() methods is important.
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);
}
}
}
}
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);
}
}
}
@eborn
Copy link

eborn commented Nov 7, 2016

Hello, what is the license for this Gist?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment