Skip to content

Instantly share code, notes, and snippets.

@jonashw
Last active June 20, 2016 18:24
Show Gist options
  • Save jonashw/37040ee0cd81c8b37831eadacb5a3d54 to your computer and use it in GitHub Desktop.
Save jonashw/37040ee0cd81c8b37831eadacb5a3d54 to your computer and use it in GitHub Desktop.
This is typically how I implement a sum type in C# for day-to-day use. At the bottom is the code I wish I could write instead.
//So much boilerplate!
public abstract class UserAccountCreationResult
{
private UserAccountCreationResult() { }
public UserAccountCreationResult AsBaseType() => this;
//Emulate pattern matching
public abstract T Match<T>(
Func<Success, T> success,
Func<WeakPassword, T> weakPassword,
Func<Duplicate, T> duplicate,
Func<Error, T> error);
//This is mainly for easier debugging:
public override string ToString() =>
Match(
success => $"Success({success.UserAccountToken})",
weakPassword => $"Weak Password({weakPassword.Weakness})",
duplicate => $"Duplicate({duplicate.Column})",
error => $"Error({error.Message})");
public sealed class Success : UserAccountCreationResult
{
public readonly Guid UserAccountToken;
internal Success(Guid userAccountToken)
{
UserAccountToken = userAccountToken;
}
public override T Match<T>(
Func<Success, T> success,
Func<WeakPassword, T> weakPassword,
Func<Duplicate, T> duplicate,
Func<Error, T> error) => success(this);
}
public sealed class WeakPassword : UserAccountCreationResult
{
public readonly PasswordWeakness Weakness;
internal WeakPassword(PasswordWeakness weakness)
{
Weakness = weakness;
}
public override T Match<T>(
Func<Success, T> success,
Func<WeakPassword, T> weakPassword,
Func<Duplicate, T> duplicate,
Func<Error, T> error) => weakPassword(this);
}
public sealed class Duplicate : UserAccountCreationResult
{
public readonly UserAccountColumn Column;
internal Duplicate(UserAccountColumn column)
{
Column = column;
}
public override T Match<T>(
Func<Success, T> success,
Func<WeakPassword, T> weakPassword,
Func<Duplicate, T> duplicate,
Func<Error, T> error) => duplicate(this);
}
public sealed class Error : UserAccountCreationResult
{
public readonly string Message;
internal Error(string message)
{
Message = message;
}
public override T Match<T>(
Func<Success, T> success,
Func<WeakPassword, T> weakPassword,
Func<Duplicate, T> duplicate,
Func<Error, T> error) => error(this);
}
}
public enum PasswordWeakness
{
NoNumber,
NoCapitalLetterOrSpecial,
TooShort
}
public enum UserAccountColumn
{
Email, Phone
}
/* Below is the code I'd *like* to write instead.
* The SumType attribute indicates that a Match method is to be generated and ToString overridden.
* The SumTypeVariant attribute indicates an argument to the generated Match method signature, and implements the Match method.
* The SumTypeVariant attribute also generates a constructor for the readonly members of the subclass.
*/
[SumType]
public abstract class UserAccountCreationResultWithCodeGeneration
{
[SumTypeVariant]
public sealed class Success : UserAccountCreationResult
{
public readonly Guid UserAccountToken;
}
[SumTypeVariant]
public sealed class WeakPassword : UserAccountCreationResult
{
public readonly PasswordWeakness Weakness;
}
[SumTypeVariant]
public sealed class Duplicate : UserAccountCreationResult
{
public readonly UserAccountColumn Column;
}
[SumTypeVariant]
public sealed class Error : UserAccountCreationResult
{
public readonly string Message;
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment