3

If I have parts of code that need to mutate the underlying data structure but then others that consume, not mutate the structure, is it good practice to create a wrapper which does not expose mutation operations?

Let's say I have a tree and I only want higher up parts of code to be able to iterate it.

5
  • 3
    What do you mean by "good practice?" Feb 22, 2015 at 14:10
  • Good read. :) I'm looking more for pitfalls that might occur. I really want to protect the data structure. Feb 22, 2015 at 14:14
  • Is it a good idea to reduce the cognitive load somewhat? Yes.
    – Den
    Feb 23, 2015 at 9:10
  • What if you have an immutable container, that contains a mutable container what would you have? I wouldn't call it immutable.
    – Pieter B
    Feb 23, 2015 at 13:37
  • Be very careful here, as immutable wrappers are not able to protect against reference types being mutated by anyone who gets their hands on a reference to that object. I'm not quite sure if you've considered these issues in your chosen approach, and I'm wondering if your question is in XY problem territory because of it.
    – Flater
    Nov 28, 2022 at 0:21

5 Answers 5

3

It's a common approach. Conceptually, this is exactly the same principle as access specifiers and such, namely that you are limiting what operations can be used so that you know that the code is safe.

It can be a lot of effort and confusion to produce wrappers for each possible situation, so it's only really worth it if there's a fairly large body of code that needs this specific subset.

In general, it's always a good thing to restrict methods as much as possible. The only question is how much effort it's worth. This is not a tradeoff that can be made for you without really knowing how much effort it'll cost you and how much code can use it.

3

Yes, encapsulation and immutability are good, but it's important to understand why, because you're sort of hitting just the wrong spot as Erik Meijer describes in his paper subtitled "Mostly functional" programming does not work.

Encapsulation is good when it works to decouple an interface from its implementation. It stops being good when you look at it to be some sort of security system protecting your public data from all those evil programmers down the hall who write the calling code. What often ends up happening in that situation is more code needs the unrestricted reference than you anticipated, and you end up with a lot of hacks converting it back and forth, or with the one unrestricted class trying to do way too much.

Immutability has a great number of benefits, nearly all of which disappear when you try to do it halfway. For example, you won't be able to change the tree without resetting every iterator in every thread currently accessing it.

You're really better off just making it immutable all the way down. This lets you take advantage of all the benefits of immutability, and you won't care who has access to the insert/delete API, because they're only changing their version of the tree.

0

Immutability has two effects: One, I cannot modify an immutable object. Two, nobody can modify an immutable object behind my back. If you hand me an immutable container containing a mutable object, then you can still modify that mutable object in the immutable container behind my back, so I gain nothing from the fact that it is immutable. You'd have to guarantee that once you create the immutable container, you are not going to modify the object anymore.

On the other hand, you can discuss whether an immutable container of mutable objects is really "immutable". Like an immutable array of strings, where I can rely that the array will always contain the same strings, but I cannot rely on what the mutable strings contain. For many situations, that kind of immutability doesn't actually give the guarantees that a consumer needs.

3
  • So, in a nutshell, having an immutable container containing only mutable content does not add any value?
    – valenterry
    Feb 23, 2015 at 9:38
  • An immutable container of mutable objects is immutable if no chain of events would cause a reference to those mutable objects to be exposed to code that would mutate them. If the code which creates the container has never exposed the mutable object to anything that might mutate it, and it knows that the container doesn't do so either, then the code which creates the container can know that it is immutable. The difficulty is that recipients of the container can't know what degree of care its creator took in guarding its contents, unless the container itself creates those contents.
    – supercat
    Jul 27, 2015 at 19:58
  • There are situations where it’s good enough to guarantee that an array always contains the same objects, or a dictionary/map contains the same keys and same value objects even if the objects are mutated. Sometimes, not often. For example an array of text fields. The text fields are changing, the objects will be the same.
    – gnasher729
    Nov 27, 2022 at 23:26
0

Immutability is not an end goal, in and of itself. Immutability is a tool. Like all tools, it has its appropriate uses, its advantages and disadvantages. Achieving it with existing mutable objects is harder than it looks.

Consider an ordinary array:

var pokerHand = new Cards[5];

We can make a data structure using an array as a backing field, and writing an indexer for it:

public class ImmutableArray<T>
{
    T[] array;

    public this T(int index)
    {
        get { return array[index]; }
    }
}

By eliminating the setter from the indexer, you've effectively made member access immutable, right? Well, not exactly. Because the item returned by the indexer is a reference type, you can still replace the object held by that reference type, and now you have a new object in that position in the array.

var item = myPokerHand[5];
item = new Card("KS"); // myPokerHand[5] now contains a new Card

To protect the contents of the original array, you have to make a copy of the object, and return that instead.

    public this T(int index)
    {
        get { return array[index].MemberwiseClone(); } // if you can.
    }

Note that MemberwiseClone is a shallow copy, not a deep copy. Doing this correctly could mean writing a Clone method that faithfully copies all of your object's members, some of which may also be mutable references.

For all of these reasons, it is better to use the immutable collections already provided by the .NET framework, such as ReadOnlyCollection, collections that are designed from the ground up to be immutable.

For your own collections and objects, routinely restricting access to private members is generally a good thing, for reasons that I hope are obvious (cf. encapsulation).

Further Reading

Immutability in C# Part One: Kinds of Immutability

1
  • 2
    There is I believe a mistake in item = new Card("KS"); // myPokerHand[5] now contains a new Card. Here is a piece of code which shows that the element remains unchanged. Maybe you meant changing a property of a card instead? Feb 27, 2015 at 8:08
-3

Restriction of access is always a safe approach and can be considered good practice. This is known under the OOP paradigm as encapsulation and exists to prevent programming mistakes which are hard to detect, such as circumventing your own safety precautions in get/set methods by directly changing a variable. This also falls under the broader term of defensive programming, which is a programming paradigm where you eliminate potential errors by making it impossible to commit them in the first place.

There are many different way of achieving this and wrapping portions of code in immutable data structures is definately a viable option, depending on the language.

12
  • 3
    Why? Just you saying so doesn't make it true without a rationale. Feb 22, 2015 at 14:07
  • 1
    I imagine there might be some pitfalls like over engineering. Is this not the case? Feb 22, 2015 at 14:10
  • Encapsulation is one of the four fundamentals of OOP. I'd consider it good practice. Feb 22, 2015 at 14:12
  • 1
    Defensive programming is often over engineering, and you won't know if it helped until you didn't do it. Feb 22, 2015 at 14:14
  • 1
    Describe that reason, if you think it's relevant to the question. I suspect we're already beyond simple access restriction, however. Immutability is a whole scene in and of itself. Feb 22, 2015 at 14:22

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.