In many cases, we tend to overlook important abstractions in our object models. Emails and Phone Numbers are two common ones. A lot of times these are immutable value objects, to borrow from DDD. A lot of these guys are perfect for operator overloading. Here's a simple example:
public class Email : IEquatable<Email>
{
public Email(string address)
{
if (string.IsNullOrEmpty(address) || ! Validate(address))
{
throw new InvalidEmailException(address);
}
_address = address;
}
private bool Validate(string address)
{
//insert simple regex here
return true;
}
private readonly string _address;
public string Address
{
get { return _address; }
}
public static implicit operator Email(string address)
{
return new Email(address);
}
public static implicit operator string(Email email)
{
return email.Address;
}
public bool Equals(Email email)
{
if (email == null) return false;
return Equals(_address, email._address);
}
public override bool Equals(object obj)
{
if (ReferenceEquals(this, obj)) return true;
return Equals(obj as Email);
}
public override int GetHashCode()
{
return _address.GetHashCode();
}
}
There's a bit of noise there, but here's what it gives us:
Email me = "myfirstname@evanhoff.com";
string address = me;
How about a little design by contract goodness:
public class EmailSender
{
public void Send(Email to, Email from, string subject, string body)
{
//implementation
}
}
With usage:
EmailSender sender = new EmailSender();
sender.Send("myfirstname@evanhoff.com", "johndoe@hotmail.com", "test subject", "test body");
You'll notice that the compiler will automagically use the implicit string conversion. In addition, we have DbC enforced as part of the conversion process. You'll also notice I overrode Equals and GetHashCode for value-based comparison semantics (ok, ok, so ReSharper overrode them for me..lol).
This works great for a lot of other "missing abstractions" also.
public class Phone : IEquatable<Phone>
{
private readonly string _number;
public Phone(string number)
{
if (string.IsNullOrEmpty(number) || ! Validate(number))
{
throw new InvalidPhoneException(number);
}
_number = number;
}
private bool Validate(string number)
{
//insert validation here
return true;
}
public string Number
{
get { return _number; }
}
public static implicit operator Phone(string number)
{
return new Phone(number);
}
public static implicit operator string(Phone phone)
{
return phone.Number;
}
public bool Equals(Phone phone)
{
if (phone == null) return false;
return Equals(_number, phone._number);
}
public override bool Equals(object obj)
{
if (ReferenceEquals(this, obj)) return true;
return Equals(obj as Phone);
}
public override int GetHashCode()
{
return _number.GetHashCode();
}
}
public class InvalidPhoneException : Exception
{
private readonly string _number;
public InvalidPhoneException(string number)
{
_number = number;
}
public string Number
{
get { return _number; }
}
public override string Message
{
get { return string.Format("Invalid Phone Number: {0}", _number); }
}
}Note: All of the above is "blog code". It doesn't have unit tests, so I'm not going to say it's bug free.