Click here to Skip to main content
15,884,473 members
Articles / Programming Languages / F#

Easy Immutable Objects in C#

Rate me:
Please Sign up or sign in to vote.
4.90/5 (28 votes)
30 Nov 2015CPOL4 min read 43.4K   18   22
Using an F# library for immutable objects reduces required code by an amazing amount. Additionally, it eradicates error prone maintenance.

Aristotle said, “Change in all things is sweet.” With all due respect to the great philosopher, he obviously never had to write software…

Head on over to Stack Overflow and search for “Immutable Object Patterns in C#” and you will see that this is a point of contention for many developers. Creating immutable objects in C# is cumbersome at best. What if I told you there is a practically effortless way to accomplish this with about one quarter of the code that is used in typical examples. I knew that would get your attention! Hang on tight while I walk you through an exciting demonstration!

As a side note, I’m not going to try to sell anyone on immutability in this post. There are countless other articles out there with that purpose. From here forward, I’m going to assume the reader understands why and when they need immutable objects.

The Problem

It’s a typical Friday night, when the phone rings. It’s NASA again (sigh…), and they need another brilliant celestial program in the key of C#. So much for relaxing, duty calls… After contemplating the specs, it’s obvious that an immutable planet class is a requirement. Let’s get started.

Typical C# Solution

In order to create the planet object in C#, you must create a constructor that accepts all property values and then sets those property values in the constructor body. This is a fairly recent addition to C#. In older versions, you also had to create backing fields for each property. Additionally, it’s wise to override the equals method because by default C# uses reference equality. This could be a topic for debate and your mileage may vary; however, for the sake of argument, let’s just assume we want value equality for planet objects. Another debatable topic may be whether to use a struct or a class. This example uses a class because C# will always create default parameterless constructors for structs. It doesn’t really make sense to have an instance of a planet that cannot change and is initialized without values. The result is the code below.

C#
public class Planet
    {
        public Planet(
            string name,
            decimal massKg,
            decimal equatorialDiameterKm,
            decimal polarDiameterKm,
            decimal equatorialCircumferenceKm,
            decimal orbitalDistanceKm,
            decimal orbitPeriodEarthDays,
            decimal minSurfaceTemperatureCelsius,
            decimal maxSurfaceTemperatureCelsius)
        {
            this.Name = name;
            this.MassKg = massKg;
            this.EquatorialDiameterKm = equatorialDiameterKm;
            this.PolarDiameterKm = polarDiameterKm;
            this.EquatorialCircumferenceKm = equatorialCircumferenceKm;
            this.OrbitalDistanceKm = orbitalDistanceKm;
            this.OrbitPeriodEarthDays = orbitPeriodEarthDays;
            this.MinSurfaceTemperatureCelsius = minSurfaceTemperatureCelsius;
            this.MaxSurfaceTemperatureCelsius = maxSurfaceTemperatureCelsius;
        }

        public string Name { get; }

        public decimal MassKg { get; }

        public decimal EquatorialDiameterKm { get; }

        public decimal PolarDiameterKm { get; }

        public decimal EquatorialCircumferenceKm { get; }

        public decimal OrbitalDistanceKm { get; }

        public decimal OrbitPeriodEarthDays { get; }

        public decimal MinSurfaceTemperatureCelsius { get; }

        public decimal MaxSurfaceTemperatureCelsius { get; }

        public override bool Equals(object obj)
        {
            if (ReferenceEquals(null, obj))
            {
                return false;
            }

            if (ReferenceEquals(this, obj))
            {
                return true;
            }

            return obj.GetType() == this.GetType() && this.Equals((Planet)obj);
        }

        protected bool Equals(Planet other)
            =>
                string.Equals(this.Name, other.Name) && this.MassKg == other.MassKg && this.EquatorialDiameterKm == other.EquatorialDiameterKm
                && this.PolarDiameterKm == other.PolarDiameterKm && this.EquatorialCircumferenceKm == other.EquatorialCircumferenceKm
                && this.OrbitalDistanceKm == other.OrbitalDistanceKm && this.OrbitPeriodEarthDays == other.OrbitPeriodEarthDays
                && this.MinSurfaceTemperatureCelsius == other.MinSurfaceTemperatureCelsius && this.MaxSurfaceTemperatureCelsius == other.MaxSurfaceTemperatureCelsius;

        public override int GetHashCode()
        {
            unchecked
            {
                var hashCode = (this.Name?.GetHashCode() ?? 0);
                hashCode = (hashCode * 397) ^ this.MassKg.GetHashCode();
                hashCode = (hashCode * 397) ^ this.EquatorialDiameterKm.GetHashCode();
                hashCode = (hashCode * 397) ^ this.PolarDiameterKm.GetHashCode();
                hashCode = (hashCode * 397) ^ this.EquatorialCircumferenceKm.GetHashCode();
                hashCode = (hashCode * 397) ^ this.OrbitalDistanceKm.GetHashCode();
                hashCode = (hashCode * 397) ^ this.OrbitPeriodEarthDays.GetHashCode();
                hashCode = (hashCode * 397) ^ this.MinSurfaceTemperatureCelsius.GetHashCode();
                hashCode = (hashCode * 397) ^ this.MaxSurfaceTemperatureCelsius.GetHashCode();
                return hashCode;
            }
        }
    }

Wow, that’s a lot of code. Looking ahead, let’s imagine what it will take to add a new property in the future. All the following changes are required:

  1. Constructor arguments
  2. Constructor body
  3. Properties
  4. Equals method
  5. GetHashCode method

That’s a ton of changes with several opportunities for error. All that typing! My fingers are sore!

A Better Way

Fortunately, there is a better way to create the planet class! The answer is, use F#. I know what you’re thinking, “Wait, hold the presses! Look at the title of this blog post!”. Hold on, allow me to elucidate. I’ll first put you at ease by telling you that you don’t even need to learn F# to use this solution. You just need to create a simple F# project and reference it from your C# project. Because F# is a .NET language, F# libraries are entirely accessible to C#.

F# has a construct known as a record type that mimics the behavior of the C# class shown above. The best part is that record types are effortless to define. Below is the code required to create a planet class that behaves identically to the C# class defined above.

F#
type Planet = { Name: string; MassKg: decimal; EquatorialDiameterKm: decimal; 
PolarDiameterKm: decimal; EquatorialCircumferenceKm: decimal; OrbitalDistanceKm: decimal; 
OrbitPeriodEarthDays: decimal; MinSurfaceTemperatureCelsius: decimal; 
MaxSurfaceTemperatureCelsius: decimal }

You don’t get much more concise and maintainable than that! Adding new properties is a relative breeze.

What I like most about this solution is that it acts as a “gateway drug”. There are many situations where F# offers significant advantages. However, many developers are intimidated by taking a big leap into a new language. How do you sell it to your boss? Where do you begin? This is a very pain free way to get your F# “foot in door”.

Conclusion

Using an F# library for immutable objects reduces required code by an amazing amount. Additionally, it eradicates error prone maintenance. With this new weapon in our arsenal, we can knock out that program for NASA and be done in time to curl up on the couch with a cigar and scotch before bed. Yay for humanity!

If you need more concrete examples, I encourage you to go have a look at the accompanying github repository: https://github.com/dalealleshouse/ImmutableExample. Download the source code, and notice that there are 4 projects. A C# domain project, an F# domain project, a C# planet repository project, and a test project. Change the reference in the planet repository project back and forth from the C# and F# projects in order to demonstrate the solution.

I hope this solution saves you as much time and trouble as it has me. As always, thank you for reading and feel free to contact me with any questions.

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)


Written By
Software Developer (Senior)
United States United States
I’m a passionate developer with more than 20 years of experience in countless different languages. Currently, I’m a senior software engineer for a research company where I specialize in full stack web development with .NET. I have extensive experience with ASP.NET, MVC, C#, Azure, JavaScript, JQuery, AngluarJS, TypeScript, NoSQL (RavenDB), and SQL Server.

Comments and Discussions

 
Questionmy vote of 5 Pin
Ashraf Azmi12-Dec-15 18:03
Ashraf Azmi12-Dec-15 18:03 
GeneralMy vote of 5 Pin
Daniel Miller5-Dec-15 7:06
professionalDaniel Miller5-Dec-15 7:06 
GeneralMy vote of 5 Pin
Santhakumar M4-Dec-15 8:12
professionalSanthakumar M4-Dec-15 8:12 
GeneralMy vote of 5 Pin
MrShadowGames2-Dec-15 6:35
professionalMrShadowGames2-Dec-15 6:35 
QuestionGreat Article. Might not work for JSON addicts with nullable types Pin
PCoffey2-Dec-15 6:05
PCoffey2-Dec-15 6:05 
AnswerRe: Great Article. Might not work for JSON addicts with nullable types Pin
Hideous Humpback Freak2-Dec-15 7:41
Hideous Humpback Freak2-Dec-15 7:41 
QuestionAnother alternative Pin
ozbear1-Dec-15 15:41
ozbear1-Dec-15 15:41 
AnswerRe: Another alternative Pin
Hideous Humpback Freak2-Dec-15 2:09
Hideous Humpback Freak2-Dec-15 2:09 
GeneralRe: Another alternative Pin
ozbear2-Dec-15 13:55
ozbear2-Dec-15 13:55 
GeneralRe: Another alternative Pin
Hideous Humpback Freak2-Dec-15 15:14
Hideous Humpback Freak2-Dec-15 15:14 
GeneralRe: Another alternative Pin
ozbear3-Dec-15 13:25
ozbear3-Dec-15 13:25 
GeneralRe: Another alternative Pin
Hideous Humpback Freak4-Dec-15 1:16
Hideous Humpback Freak4-Dec-15 1:16 
GeneralRe: Another alternative Pin
ozbear6-Dec-15 14:07
ozbear6-Dec-15 14:07 
GeneralRe: Another alternative Pin
brad phelan17-Jan-17 2:16
brad phelan17-Jan-17 2:16 
GeneralMy vote of 5 Pin
Guirec1-Dec-15 12:47
professionalGuirec1-Dec-15 12:47 
Elegant
QuestionC# vs. F# Pin
johannesnestler1-Dec-15 4:24
johannesnestler1-Dec-15 4:24 
AnswerRe: C# vs. F# Pin
PCoffey1-Dec-15 11:52
PCoffey1-Dec-15 11:52 
GeneralRe: C# vs. F# Pin
Hideous Humpback Freak1-Dec-15 14:25
Hideous Humpback Freak1-Dec-15 14:25 
GeneralRe: C# vs. F# Pin
PCoffey2-Dec-15 5:38
PCoffey2-Dec-15 5:38 
GeneralRe: C# vs. F# Pin
johannesnestler1-Dec-15 22:15
johannesnestler1-Dec-15 22:15 
PraiseNice idea Pin
Mark_Shield1-Dec-15 1:42
Mark_Shield1-Dec-15 1:42 
Praisevery nice Pin
BillW3330-Nov-15 10:38
professionalBillW3330-Nov-15 10:38 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.