Skip to content

C# implementation of the "Value Object" pattern as described in the "Base Patterns" section of "Patterns of Enterprise Application Architecture" (Fowler et al, 2002)

Notifications You must be signed in to change notification settings

kaiosilveira/poeaa-value-object

Repository files navigation

ℹ️ This repository is part of my "Patterns of Enterprise Application Architecture" (PoEAA) catalog, based on Martin Fowler's book with the same title. For my full work on the topic, see kaiosilveira/poeaa

Continuous Integration

Value Object

A small simple object, like money or a date range, whose equality isn’t based on identity.

Implementation example

A Tag can be modeled as a value object. Simple tags contain only a text label and are considered equal if the text in one tag matches the text in another.

Implementation considerations

To implement a value object in C#, we need to override the Equals and GetHashCode methods. These methods are used internally by the language to perform object equality comparisons and their defaults are based on comparing memory addresses. As we are only interested in the value equality and not necessarily in the memory address of each object, we can override Equals to compare a tag's text to some other tag's text. We also need to make sure that GetHashCode uses the hash code of the tag's text itself, so it's correctly found inside a HashSet and other hash-code-based data structures.

Test suite

In a good TDD fashion, unit tests were used to guide this implementation. The rationale for each test and its implementation are described below.

  • A Tag isn't equal to null:
  [Fact]
  public void TestIsNotEqualNull()
  {
    var tag = new Tag(text: "work");
    Assert.False(tag.Equals(null));
  }
  • A Tag isn't equal another object with a different type:
  [Fact]
  public void TestIsNotEqualDifferentInstance()
  {
    var tag = new Tag(text: "work");
    var person = new Person(firstName: "Kaio", lastName: "Silveira");
    Assert.False(tag.Equals(person));
  }
  • A Tag should be equal another tag with the same title:
  [Fact]
  public void TestIsEqualAnotherTagWithSameText()
  {
    var tag1 = new Tag(text: "work");
    var tag2 = new Tag(text: "work");
    Assert.Equal(tag1, tag2);
  }
  • A Tag should be correctly found inside a hash set:
  [Fact]
  public void TestIsFoundCorrectlyInsideHashSets()
  {
    var tag = new Tag(text: "work");
    var hashSet = new HashSet<Tag>();

    hashSet.Add(tag);
    Assert.Single(hashSet);

    var result = new Tag(text: "");
    Assert.True(hashSet.TryGetValue(tag, out result));
    Assert.Equal(tag, result);
  }

The full test suite is available at TagTest.cs.

Implementation details

As described above, we need to override the Tag.Equals and Tag.GetHashCode methods. The following sections go through these overwrites.

Overwriting Tag.Equals

We can apply the logic described in the section above to override the Equals method:

// class Tag
public override bool Equals(object? obj)
{
  var anotherTag = (Tag)obj;
  return this.Text == anotherTag.Text;
}

This would make the compiler to implode with many warnings related to possibly null references and unsafe casts, though. To address these concerns, we can apply some defensive programming. The final code would look like this:

// class Tag
public override bool Equals(object? obj)
{
  if (obj == null) return false;

  try
  {
    var anotherTag = (Tag)obj;
    return this.Text == anotherTag.Text;
  }
  catch (InvalidCastException)
  {
    return false;
  }
}

Overwriting Tag.GetHashCode

To overwrite the GetHashCode method, we need whether the object has any value in the first place. If that's the case, as it only has a single field, we can return the hash code of its Text. The code looks like this:

// class Tag
public override int GetHashCode()
{
  if (String.IsNullOrEmpty(this.Text)) return 0;
  return this.Text.GetHashCode();
}

About

C# implementation of the "Value Object" pattern as described in the "Base Patterns" section of "Patterns of Enterprise Application Architecture" (Fowler et al, 2002)

Topics

Resources

Stars

Watchers

Forks

Languages