Search code examples
c#oopencapsulationsolid-principlesdesign-principles

A very common C# pattern that breaks a very fundamental OOP principle


Here is a very simple question, which I'm still very uneasy about:

Why is it widely accepted now for a class to return a reference to its private member through an accessor method? Doesn't this totally break the encapsulation principle? If this is OK, then why not just make the member public!?

public class EncapsulationViolator
{
  private object abuseMe;

  public object AbuseMe 
  {
    get { return abuseMe; }
  }
}

Edit The case I was considering is this

EncapsulationViolator ev = new EncapsulationViolator();

object o = ev.AbuseMe;

o.SetValue(newValue);

Now ev's state has change through transitivity because its member abuseMe's state has changed.

In the context of DDD, this is not OK if the object is an Aggregate Root. I quote

Allow external objects to hold references to the root only. Transient references to internal members can be passed out for use within a single operation only. Because the root controls access, it cannot be blindsided by changes to the internals.

[Domain-Driven Design, Eric Evans]

... setters schmetters ...


Solution

  • You're conflating the C++ term "reference" with the fact that C# passes objects by value (of their reference).

    In this case the caller of the getter AbuseMe cannot swap out the private field abuseMe. Thus, there is no violation of encapsulation.

    EncapsulationViolator x = new EncapsulationViolator();
    object y = x.AbuseMe;
    y = 17; // I have not changed x.AbuseMe
    
    Debug.Assert(y != x.AbuseMe); // Passes!
    

    Furthermore, property getters and setters allow for proper encapsulation of private fields and is functionally identical to implementing these as methods (in fact they are implemented as methods by the compiler).

    One case in which returning a private variable could break encapsulation is when you return a reference to an array:

    class X
    {
        private int[] amazing = new int[10];
    
        public int[] Amazing { get { return this.amazing; } }
    }
    
    X a = new X();
    int[] x = a.Amazing;
    int[] y = a.Amazing;
    
    x[2] = 9;
    Debug.Assert(x[2] != y[2]); // Fails!