Last active
February 26, 2024 07:36
-
-
Save BennieCopeland/e00365feaf91d4f7c468683ba5725336 to your computer and use it in GitHub Desktop.
C# Discriminated Union pattern
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
public record EmailAddress | |
{ | |
public const int MaxSize = 255; | |
private readonly string _value; | |
private EmailAddress(string value) => _value = value; | |
public static EmailAddress FromString(string value) | |
{ | |
if (string.IsNullOrWhiteSpace(value)) | |
{ | |
throw new ArgumentException("Email address cannot be empty"); | |
} | |
if (value.Length > MaxSize) | |
{ | |
throw new ArgumentException($"Email address cannot be longer than {MaxSize} characters"); | |
} | |
return new EmailAddress(value); | |
} | |
public string AsString() | |
{ | |
return _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
public record TelephoneNumber | |
{ | |
public const int MaxSize = 50; | |
private readonly string _value; | |
private TelephoneNumber(string value) => _value = value; | |
public static Result<TelephoneNumber, string> FromString(string value) | |
{ | |
if (string.IsNullOrWhiteSpace(value)) | |
{ | |
throw new ArgumentException("Telephone number cannot be empty"); | |
} | |
if (value.Length > MaxSize) | |
{ | |
throw new ArgumentException($"Telephone number cannot be longer than {MaxSize} characters"); | |
} | |
return new TelephoneNumber(value); | |
} | |
public string AsString() | |
{ | |
return _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
public abstract record ContactMethod | |
{ | |
public static ContactMethod Email(EmailAddress emailAddress) => new EmailMethod(emailAddress); | |
public static ContactMethod Telephone(TelephoneNumber telephoneNumber) => new PhoneMethod(telephoneNumber); | |
public static ContactMethod CarrierPigeon(string pigeonName) => new CarrierPigeonMethod(pigeonName); | |
public abstract T Match<T>(Func<EmailAddress, T> email, Func<TelephoneNumber, T> phone, Func<string, T> carrierPigeon); | |
private sealed record EmailMethod : ContactMethod | |
{ | |
private readonly EmailAddress _emailAddress; | |
public EmailMethod(EmailAddress emailAddress) => _emailAddress = emailAddress; | |
public override T Match<T>(Func<EmailAddress, T> email, Func<TelephoneNumber, T> phone, Func<string, T> carrierPigeon) | |
{ | |
return email(_emailAddress); | |
} | |
} | |
private sealed record PhoneMethod : ContactMethod | |
{ | |
private readonly TelephoneNumber _telephoneNumber; | |
public PhoneMethod(TelephoneNumber telephoneNumber) => _telephoneNumber = telephoneNumber; | |
public override T Match<T>(Func<EmailAddress, T> email, Func<TelephoneNumber, T> phone, Func<string, T> carrierPigeon) | |
{ | |
return phone(_telephoneNumber); | |
} | |
} | |
private sealed record CarrierPigeonMethod : ContactMethod | |
{ | |
private readonly string _pigeonName; | |
public CarrierPigeonMethod(string pigeonName) => _pigeonName = pigeonName; | |
public override T Match<T>(Func<EmailAddress, T> email, Func<TelephoneNumber, T> phone, Func<string, T> carrierPigeon) | |
{ | |
return carrierPigeon(_pigeonName); | |
} | |
} | |
} |
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
class User | |
{ | |
public ContactMethod? ContactMethod { get; private set; } | |
public void AddEmailContact(EmailAddress emailAddress) => ContactMethod = ContactMethod.Email(emailAddress); | |
public void AddPhoneContact(TelephoneNumber telephoneNumber) => ContactMethod = ContactMethod.Telephone(telephoneNumber); | |
public void AddCarrierPigeon(string pigeonName) => ContactMethod = ContactMethod.CarrierPigeon(pigeonName); | |
} | |
interface IServices | |
{ | |
void SendEmail(EmailAddress emailAddress); | |
void CallPhone(TelephoneNumber telephoneNumber); | |
void SendPigeon(string pigeonName); | |
} | |
class UserService | |
{ | |
private readonly IServices _services; | |
public UserService(IServices services) => _services = services; | |
public void ContactUser(User user) | |
{ | |
if (user.ContactMethod == null) throw new Exception("Can't contact user"); | |
user.ContactMethod.Match( | |
emailAddress => _services.SendEmail(emailAddress), | |
telephoneNumber => _services.CallPhone(telephoneNumber), | |
pigeonName => _services.SendPigeon(pigeonName)); | |
} | |
} |
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
type EmailAddress = EmailAddress of string | |
with | |
static member MaxValue = 150 | |
static member FromString str = | |
if System.String.IsNullOrWhiteSpace str then | |
Error "Email address cannot be empty" | |
elif str.Length > EmailAddress.MaxValue then | |
Error $"Email address cannot be longer that {EmailAddress.MaxValue} characters" | |
else | |
Ok (EmailAddress str) | |
static member AsString (EmailAddress emailAddress) = emailAddress | |
type TelephoneNumber = TelephoneNumber of string | |
with | |
static member MaxValue = 50 | |
static member FromString str = | |
if System.String.IsNullOrWhiteSpace str then | |
Error "Telephone number cannot be empty" | |
elif str.Length > TelephoneNumber.MaxValue then | |
Error $"Telephone number cannot be longer that {TelephoneNumber.MaxValue} characters" | |
else | |
Ok (TelephoneNumber str) | |
static member AsString (TelephoneNumber telephoneNumber) = telephoneNumber | |
type ContactMethod = | |
| Email of EmailAddress | |
| Telephone of TelephoneNumber | |
| CarrierPigeon of string | |
let isEmail = function | |
| Email _ -> true | |
| _ -> false | |
let isTelephone = function | |
| Telephone _ -> true | |
| _ -> false | |
let isCarrierPigeon = function | |
| CarrierPigeon _ -> true | |
| _ -> false | |
type User = | |
{ | |
ContactMethod : ContactMethod option | |
} | |
let addEmailContact user emailAddress = | |
{ user with ContactMethod = Some (Email emailAddress) } | |
let addPhoneContact user phoneNumber = | |
{ user with ContactMethod = Some (Telephone phoneNumber) } | |
let addCarrierPigeon user pigeonName = | |
{ user with ContactMethod = Some (CarrierPigeon pigeonName) } | |
type Services = | |
{ | |
sendEmail : EmailAddress -> unit | |
callPhone : TelephoneNumber -> unit | |
sendPigeon : string -> unit | |
} | |
let contactUser services user = | |
match user.ContactMethod with | |
| Some (Email emailAddress) -> services.sendEmail emailAddress | |
| Some (Telephone telephoneNumber) -> services.callPhone telephoneNumber | |
| Some (CarrierPigeon pigeonName) -> services.sendPigeon pigeonName | |
| None -> failwith "Can't contact user" |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment