Is there an easy way to make an immutable version of a class?
Asked Answered
M

8

13

Is there an easy way to make an instance immutable?

Let's do an example, I have a class holding a lots of data fields (only data, no behavior):

class MyObject
{
    // lots of fields painful to initialize all at once
    // so we make fields mutable :

    public String Title { get; set; }
    public String Author { get; set; }

    // ...
}

Example of creation:

MyObject CreationExample(String someParameters)
{
    var obj = new MyObject
    {
        Title = "foo"
        // lots of fields initialization
    };

    // even more fields initialization
    obj.Author = "bar";

    return obj;
}

But now that I have fully created my object, I don't want the object to be mutable anymore (because the data consumer will never need to change the state), so I would like something like that List.AsReadOnly:

var immutableObj = obj.AsReadOnly();

But if I want this behavior, I need to make another class that have exactly the same fields but without setter.

So is there any automatic way to generate this immutable class ? Or another way to allow mutability during creation but immutable once initialized ?

I know that fields can be marked as "readonly", but the object will be initialized outside of the class, and passing all fields as constructor parameters seems like a bad idea (too much parameters).

Masked answered 29/9, 2014 at 17:35 Comment(7)
You should edit your question to make it clear that you're interested in a solution to the problem where you have a lot of fields that you don't want to initialize in the constructor call (for some reason). Off the top of my head, I'd say that if you really want to do this, you can use a mutable helper class that can hold all the data items for constructing the immutable class. Initialize that helper class piecemeal, then construct your immutable class by passing in the instance of the helper class. I'm not sure if this is a very good pattern to use though.Slippery
Search for "popsicle immutability" for excellent series by Eric LippertFarley
@AlexeiLevenkov: I see an blog posting where Lippert mentions "Popsicle immutability" (that's a great name for the idea), but I don't see any posts where he talks about a pattern for implementing it.Slippery
Related question: How to freeze a popsicle in .NET (make a class immutable). @Alexei: +1 for the reference.Segno
If there are too many parameters for a clean readonly implementation, maybe your class needs some refactoring?Bisutun
@GuillaumeCR: In most cases I agree that it would be bad design, but in my case it represent a "real world" "template", so I didn't decide how data are hierarchized and so I can't change the organization. (My comment might be unclear, so I'll get more concret, I need to represent an RSS feed, so I can't decide how RSS format is organized)Masked
Yeah I get it. Honestly though I think that compile-time features are usually worth the cost, so using 'readonly' makes you have to write long 'new' expressions, but it's totally worth it. Saves you a bunch of headaches during run-time.Bisutun
S
10

No, there is no easy way to make any type immutable, especially not if you want "deep" immutability (i.e. where no mutable object can be reached through the immutable object). You will have to explicitly design your types to be immutable. The usual mechanisms to make types immutable are these:

  • Declare (property-backing) fields readonly. (Or, starting with C# 6 / Visual Studio 2015, use read-only auto-implemented properties.)
  • Don't expose property setters, only getters.

  • In order to initialize (property-backing) fields, you must initialize them in the constructor. Therefore, pass the (property) values to the constructor.

  • Don't expose mutable objects, such as collections based on mutable-by-default types (like T[], List<T>, Dictionary<TKey,TValue>, etc.).

    If you need to expose collections, either return them in a wrapper that prevents modification (e.g. .AsReadOnly()), or at the very least return a fresh copy of the internal collection.

  • Use the Builder pattern. The following example is too trivial to do the pattern justice, because it's usually recommended in cases where non-trivial object graphs need to be created; nevertheless, the basic idea is something like this:

    class FooBuilder // mutable version used to prepare immutable objects
    {
        public int X { get; set; }
        public List<string> Ys { get; set; }
        public Foo Build()
        {
            return new Foo(x, ys);
        }
    }
    
    class Foo // immutable version
    {
        public Foo(int x, List<string> ys)
        {
            this.x = x;
            this.ys = new List<string>(ys); // create a copy, don't use the original
        }                                   // since that is beyond our control
        private readonly int x;
        private readonly List<string> ys;
        …
    }
    
Segno answered 29/9, 2014 at 17:53 Comment(0)
I
3

Hmm I will enumerate my first thought on this...

1. Use internal setters if your only worry is manipulation outside of your assembly. internal will make your properties available to classes in the same assembly only. For example:

public class X
{
    // ...
    public int Field { get; internal set; }

    // ...
}

2. I don't agree that it's necessarily a bad idea to have lots of parameters in your constructor.

3. You could generate another type at runtime that is a read-only version of your type. I can elaborate on this, but personally I think this is overkill.

Best, Iulian

Iny answered 29/9, 2014 at 17:46 Comment(1)
Additionally you could attempt to refactor into smaller classes if at all possible. E.x. being instead of "Address1, address2, city, state, zip" properties, create an object that represents "Address" with all of those as properties. Granted this might make object constructor potentially more difficult, but that's what you can use builders (and I'm sure other patterns) for.Hygiene
C
3

As another solution you can use Dynamic Proxy. Alike approach was used for Entity Framework http://blogs.msdn.com/b/adonet/archive/2009/12/22/poco-proxies-part-1.aspx. Here is example how you can do it using Castle.DynamicProxy framework. This code is based on original example from Castle Dynamic proxy (http://kozmic.net/2008/12/16/castle-dynamicproxy-tutorial-part-i-introduction/)

namespace ConsoleApplication8
{
using System;
using Castle.DynamicProxy;

internal interface IFreezable
{
    bool IsFrozen { get; }
    void Freeze();
}

public class Pet : IFreezable
{
    public virtual string Name { get; set; }
    public virtual int Age { get; set; }
    public virtual bool Deceased { get; set; }

    bool _isForzen;

    public bool IsFrozen => this._isForzen;

    public void Freeze()
    {
        this._isForzen = true;
    }

    public override string ToString()
    {
        return string.Format("Name: {0}, Age: {1}, Deceased: {2}", Name, Age, Deceased);
    }
}

[Serializable]
public class FreezableObjectInterceptor : IInterceptor
{
    public void Intercept(IInvocation invocation)
    {
        IFreezable obj = (IFreezable)invocation.InvocationTarget;
        if (obj.IsFrozen && invocation.Method.Name.StartsWith("set_", StringComparison.OrdinalIgnoreCase))
        {
            throw new NotSupportedException("Target is frozen");
        }

        invocation.Proceed();
    }
}

public static class FreezableObjectFactory
{
    private static readonly ProxyGenerator _generator = new ProxyGenerator(new PersistentProxyBuilder());

    public static TFreezable CreateInstance<TFreezable>() where TFreezable : class, new()
    {
        var freezableInterceptor = new FreezableObjectInterceptor();
        var proxy = _generator.CreateClassProxy<TFreezable>(freezableInterceptor);
        return proxy;
    }
}

class Program
{
    static void Main(string[] args)
    {
        var rex = FreezableObjectFactory.CreateInstance<Pet>();
        rex.Name = "Rex";

        Console.WriteLine(rex.ToString());
        Console.WriteLine("Add 50 years");
        rex.Age += 50;
        Console.WriteLine("Age: {0}", rex.Age);
        rex.Deceased = true;
        Console.WriteLine("Deceased: {0}", rex.Deceased);
        rex.Freeze();

        try
        {
            rex.Age++;
        }
        catch (Exception ex)
        {
            Console.WriteLine("Oups. Can't change that anymore");
        }

        Console.WriteLine("--- press enter to close");
        Console.ReadLine();
    }
}
}
Circulate answered 17/12, 2015 at 14:13 Comment(1)
You can also use ProxyGenerator.CreateClassProxyWithTarget(existingObject, interceptor). Then you will not need to implement the IFreezable interface and add any additional logic for IsFrozen. All you have to do in this case is to set all required fields beforehand and invoke the generator after that to return a proxy.Mandorla
H
2

You kind of hinted at a way in your question, but I'm not sure if this is not an option for you:

class MyObject
{
    // lots of fields painful to initialize all at once
    // so we make fields mutable :

    public String Title { get; protected set; }
    public String Author { get; protected set; }

    // ...

    public MyObject(string title, string author)
    {
        this.Title = title;
        this.Author = author;
    }
}

Due to the constructor being the only way of manipulating your Author and Title, the class is in effect immutable after construction.

EDIT:

as stakx mentioned, I too am a big fan of using builders - especially because it makes unit testing easier. For the above class you could have a builder such as:

public class MyObjectBuilder
{
    private string _author = "Default Author";
    private string _title = "Default title";

    public MyObjectBuilder WithAuthor(string author)
    {
        this._author = author;
        return this;
    }

    public MyObjectBuilder WithTitle(string title)
    {
        this._title = title;
        return this;
    }

    public MyObject Build()
    {
        return new MyObject(_title, _author);
    }
}

This way you can construct your objects with default values, or override them as you please, but MyObject's properties can't be changed after construction.

// Returns a MyObject with "Default Author", "Default Title"
MyObject obj1 = new MyObjectBuilder.Build();

// Returns a MyObject with "George R. R. Martin", "Default Title"
MyObject obj2 = new MyObjectBuilder
    .WithAuthor("George R. R. Martin")
    .Build();

If you ever need to add new properties to your class, it's much easier to go back to your unit tests that consume from a builder rather than from a hardcoded object instantiation (i don't know what to call it, so pardon my terms).

Hygiene answered 29/9, 2014 at 17:40 Comment(5)
There's a lot of fields (more than 10), passing all of them as parameters seems like not a good idea. (most fields have an acceptable default value)Masked
This solution works in this particular case, but not in general. Let's say we have a class C with a property List<string> Foos { get; protected set; }. Even though only its getter is public, objects of type C can be modified because their Foos collection can be modified: var c = new C(…); c.Foos.Add(new Foo(…)); That is, such objects are not "deeply" immutable.Segno
@lulian Does not seem like a good idea? It works. You can assign a default to null.Marked
My little answer spawned so much back and forth... go me? :OHygiene
@stakx It is not always about you. +1 is about the answer not your tangent.Marked
K
2

I would suggest having an abstract base type ReadableMyObject along with derived types MutableMyObject and ImmutableMyObject. Have constructors for all the types accept a ReadableMyObject, and have all the property setters for ReadableMyObject call an abstract ThrowIfNotMutable method before updating their backing field. Additionally, have ReadableMyObject support a public abstract AsImmutable() method.

Although this approach will require writing some boilerplate for each property of your object, that will be the extent of the required code duplication. The constructors for MutableMyObject and ImmutableMyObject will simply pass the received object to the base-class constructor. Class MutableMyObject should implement ThrowIfNotMutable to do nothing, and AsImmutable() to return new ImmutableMyObject(this);. Class ImmutableByObject should implement ThrowIfNotMutable to throw an exception, and AsImmutable() to return this;.

Code which receives a ReadableMyObject and wants to persist its contents should call its AsImmutable() method and store the resulting ImmutableMyObject. Code which receives a ReadableMyObject and wants a slightly-modified version should call new MutableMyObject(theObject) and then modify that as required.

Kristlekristo answered 29/9, 2014 at 17:58 Comment(0)
H
0

Well, if you have too many parameters and you dont want to do constructors with parameters....here is an option

class MyObject
        {
            private string _title;
            private string _author;
            public MyObject()
            {

            }

            public String Title
            {
                get
                {
                    return _title;
                }

                set
                {
                    if (String.IsNullOrWhiteSpace(_title))
                    {
                        _title = value;
                    }
                }
            }
            public String Author
            {
                get
                {
                    return _author;
                }

                set
                {
                    if (String.IsNullOrWhiteSpace(_author))
                    {
                        _author = value;
                    }
                }
            }

            // ...
        }
Halfon answered 29/9, 2014 at 17:38 Comment(6)
There's a lot of fields (more than 10), passing all of them as parameters seems like not a good idea. (most fields have an acceptable default value)Masked
@Masked well, I updated my answer ...not great solution but might help youHalfon
How is that immutable?Marked
Once the properties are set, they will not be set again cuz it checks for IsNullOrWhiteSpace before setting. Anyway , this is a poorman immutable class....you have no other optionsHalfon
OK but how would it handle default values?Marked
As I did check IsNullOrWhiteSpace , you can OR your default values in the if condition. String.IsNullOrWhiteSpace(_title) || _title=="default" something like thisHalfon
S
0

Here's another option. Declare a base class with protected members and a derived class that redefines the members such that they are public.

public abstract class MyClass
{
    public string Title { get; protected set; }
    public string Author { get; protected set; }

    public class Mutable : MyClass
    {
        public new string Title { get { return base.Title; } set { base.Title = value; } }
        public new string Author { get { return base.Author; } set { base.Author = value; } }
    }
}

Creating code will use the derived class.

MyClass immutableInstance = new MyClass.Mutable { Title = "Foo", "Author" = "Your Mom" };

But for all cases where immutability is expected, use the base class:

void DoSomething(MyClass immutableInstance) { ... }
Sturges answered 29/9, 2014 at 17:59 Comment(0)
F
0

An easy (but a bit less safer) way is to define a getter-only interface and use it after the object is built. This works similarly to casting List<T> as IReadOnlyList<T>. An additional benefit is you don't need to copy the object.

interface IMyObject
{
    String Title { get; }
    String Author { get; }
}

class MyObject : IMyObject
{
    // ... unchanged ...
}

// treat returned object as read-only
IMyObject BuildMyObject()
{
    return new MyObject() { Title = "foo", Author = "bar" };
}

Yes, users of the object can still downcast to get a mutable one (as long as MyObject remains public), but I think this is enough to express the intent that "This is a read-only object."

Fribble answered 25/4, 2023 at 8:35 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.