Practical .NET

Creating a Value Object that Developers Can Use

You can dramatically simplify your code by using value objects, but to create a value object that makes sense to the developer who uses it, you need to redefine what the equals sign means. Along the way, Peter points out some problems when you don't use classes to define your data.

In a previous column, I discussed how you could simplify your application by making more use of read-only/immutable objects. I followed that up with a column on how to actually create those objects by using structs. Using a struct allows you to create two objects with identical values, compare them using the Equals method that's built into every Microsoft .NET Framework object and get True as the result of the test:

Dim adjAbsolute1 As Adjustment
Dim adjAbsolute2 As Adjustment

adjAbsolute1 = AdjustmentFactory.GetAdjustmentByName("PayingCash")
adjAbsolute2 = AdjustmentFactory.GetAdjustmentByName("PayingCash")

If adjAbsolute1.Equals(adjAbsolute2) Then

That last test is critical to me because I want two value objects with the same values in their properties to be treated as identical (just like 1 equals 1, regardless of where those two numbers come from). Fortunately for me, that's the way the Equals operator works when comparing structs.

Unfortunately for me, no developer in his right mind is going to write his comparison code using the Equals method. Any normal developer is going to want to compare the two value types using the equals sign (=), like this:

If adjAbsolute1 = adjAbsolute2 Then

The bad news is that the equals sign will return False, even if the two Adjustments have identical values. The good news is that I can redefine what the equals sign means for my Adjustment classes to get the test result I want. Unfortunately, I'll also reveal some problems with my chosen implementation.

Redefining Equality
Redefining the equals sign is easy to do with structs: I just need to create an Operator method that defines the meaning of the equals sign for my Adjustment struct. In Visual Basic I have to do it twice because, if I define the equals sign, I must also define the not equals sign (<>). While I may have to do it twice, the actual code inside my Operator methods is trivial: I just need to call the Equals method, which already does what I want.

I've discussed redefining operators in C# and Visual Basic in more detail in an earlier column. To define an operator you must add Public Shared Operator methods to your class (in C#, you use static rather than shared) and follow the keyword Operator with the operator you're redefining (in this case, that's the equals or the not equals signs). Your methods must accept two parameters: one for the values on either side of the operator. For my Adjustment struct, the code looks like this:

Public Structure Adjustment

  Public Shared Operator =(adj1 As Adjustment, adj2 As Adjustment) As Boolean
    Return adj1.Equals(adj2)
  End Operator

  Public Shared Operator <>(adj1 As Adjustment, adj2 As Adjustment) As Boolean
    Return Not adj1.Equals(adj2)
  End Operator

In my previous column, I suggested that redefining operators is something you don't want to do because you're often creating a conflict with how the developer expects the language to behave. With value objects, however, redefining the equals sign makes sense to me because all I'm really doing is providing a wrapper for the already existing Equals method. I'm not adding or removing functionality -- I'm just making the Equals method more accessible to the developer by supporting how the developer expects the language to behave.

The Wheels Fall Off
But now I have a problem. In my case study these Adjustment value types represent changes to be made to the price of a sales order: They're used to move the price up or down depending on whether the client is entitled to a discount or has incurred some extra charge. Some discounts are percentage-based ("5 percent off for paying cash") while others are absolute amounts ("$5.00 off your second purchase"). A 5 percent discount is not equal to a $5.00 discount … at least, not most of the time. To deal with this difference, I really need two kinds of Adjustment objects: a PercentageAdjustment and an AbsoluteAdjustment and to not permit comparisons between the two different kinds of Adjustments.

The obvious solution is to have a base Adjustment that defines what any Adjustment looks like and holds any code that's common to all Adjustments. I would then create PercentageAdjustment and AbsoluteAdjustments that inherit from that base Adjustment. In those two new types, I could define their equals sign operator to only support comparison between classes of the same type (for example, AbsoluteAdjustment-to-AbsoluteAdjustment, but not AbsoluteAdjustment-to-PercentageAdjustment).

Here's where my problem appears: Structs don't support inheritance. You can only use inheritance with classes. Of course, if you don't need inheritance this isn't a problem. But, still …

So I'm going to return to this topic in a later column, show how to implement value objects using classes and solve this problem with inheritance. But I can deal with this problem using structs, also, and I'll discuss that solution in yet another column. In the meantime, you have everything you need to create simple value objects.

About the Author

Peter Vogel is a system architect and principal in PH&V Information Services. PH&V provides full-stack consulting from UX design through object modeling to database design. Peter tweets about his VSM columns with the hashtag #vogelarticles. His blog posts on user experience design can be found at http://blog.learningtree.com/tag/ui/.

comments powered by Disqus

Featured

  • Compare New GitHub Copilot Free Plan for Visual Studio/VS Code to Paid Plans

    The free plan restricts the number of completions, chat requests and access to AI models, being suitable for occasional users and small projects.

  • Diving Deep into .NET MAUI

    Ever since someone figured out that fiddling bits results in source code, developers have sought one codebase for all types of apps on all platforms, with Microsoft's latest attempt to further that effort being .NET MAUI.

  • Copilot AI Boosts Abound in New VS Code v1.96

    Microsoft improved on its new "Copilot Edit" functionality in the latest release of Visual Studio Code, v1.96, its open-source based code editor that has become the most popular in the world according to many surveys.

  • AdaBoost Regression Using C#

    Dr. James McCaffrey from Microsoft Research presents a complete end-to-end demonstration of the AdaBoost.R2 algorithm for regression problems (where the goal is to predict a single numeric value). The implementation follows the original source research paper closely, so you can use it as a guide for customization for specific scenarios.

  • Versioning and Documenting ASP.NET Core Services

    Building an API with ASP.NET Core is only half the job. If your API is going to live more than one release cycle, you're going to need to version it. If you have other people building clients for it, you're going to need to document it.

Subscribe on YouTube