After seeing this sort of issue come up in many projects in the shape of hard to spot bugs I decided to quickly write a small entry as a sort of OOP Commandment.
Thou shalt not return references to internal fields!
If you took the trouble of hiding an internal field making it private or read-only don't send it all to waste by handling it to the first caller of your class method.
A simple example of what I am talking about is as follows:
|
using System;
using System.Collections.Generic;
using System.Text;
public class MyObject
{
string m_Name;
public string Name
{
get { return m_Name; }
set { m_Name = value; }
}
}
public class MyObjectHolder
{
private MyObject m;
public MyObjectHolder()
{
m = new MyObject();
m.Name = "MyObject";
}
public MyObject GetMyObject()
{
return m;
}
public string GetName()
{
return m.Name;
}
}
class Program
{
static void Main(string[] args)
{
MyObjectHolder h = new MyObjectHolder();
Console.WriteLine(h.GetName());
// The private member now is not private anymore...
MyObject m = h.GetMyObject();
// Any changes in m will affect the original internal field of h.
m.Name = "SomeOtherName";
Console.WriteLine(h.GetName());
}
}
|
There are at least three options for this simple case. One is to turn the internal field into a ValueType, a struct in this case will fullfil the objective of holding the data.
The second would be create a temporary local variable inside GetName and assign it the same values for every field. The easiest way to get that is to make your object implement ICloneable (with a deep copy implementation).
The third option is to return an interface to the object with limited capabilities.
Here is a possible implementation:
|
public interface IMyObject
{
string GetName();
}
public class MyObject: IMyObject
{
string m_Name;
public string Name
{
get { return m_Name; }
set { m_Name = value; }
}
public string GetName()
{
return m_Name;
}
}
public class MyObjectHolder
{
private MyObject m;
public MyObjectHolder()
{
m = new MyObject();
m.Name = "MyObject";
}
public IMyObject GetMyObject()
{
return m;
}
public string GetName()
{
return m.Name;
}
}
class Program
{
static void Main(string[] args)
{
MyObjectHolder h = new MyObjectHolder();
Console.WriteLine(h.GetName());
// The private member now is not private anymore...
IMyObject m = h.GetMyObject();
// Any changes in m will affect the original internal field.
//m.Name = "SomeOtherName"; <-- error : does not contain Name.
Console.WriteLine(h.GetName());
}
}
|
Although String is also a reference type it is also an immutable type so it’s ok to return a direct reference to the internal string field. Any attempt to modify it will create a new string.
A caveat of the interface solution is that the client of your class can still guess the real type of the interface reference returned and force a cast. But then again if a developer goes through such an effort he deserves what he gets.
That’s it. I hope that helps you to avoid a couple of bugs here and there.