Posts
58
Comments
103
Trackbacks
10
Operator Overloads and Value Objects

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.
posted on Wednesday, October 10, 2007 11:40 PM Print