I have designed an immutable class, because I want to have value-semantics for it. I wrote a hint into the commentary section of the class
// "This class is immutable, don't change this when adding new features to it."
But I know, sometimes those commentaries are overlooked by other team members, so I would like to create a unit test as an additional safeguard. Any idea how to accomplish this? Can one inspect a class via reflection to make sure only the constructors change it's inner state?
(Using C# 2.0 and NUnit, if that's important for anyone).
Easier testing and debugging - Since the state of your immutable object doesn't change, it's much easier to test your code, and track down bugs.
The speed of detecting non-working code depends on the tools used for continuous integration. Tests can be set to run either a one-time check at a certain time interval or can be run immediately in real-time to review changes. In short, unit tests help developers detect problems immediately, then fix them quickly.
An example to back up my comment on how you can use FieldInfo.IsInitOnly
recursively to test for immutability.
There may be more special cases to consider like how I have handled string
, but it will only give false negatives I believe, i.e. will tell you something is mutable that is not, not the other way around.
The logic is, every field must be readonly
and be an immutable type itself. Note that it will not cope with self referential types or circular references.
using System;
using System.Linq;
using System.Reflection;
using Microsoft.VisualStudio.TestTools.UnitTesting;
namespace ImmutableTests
{
[TestClass]
public class AssertImmutableTests
{
[TestMethod]
public void Is_int_immutable()
{
Assert.IsTrue(Immutable<int>());
}
[TestMethod]
public void Is_string_immutable()
{
Assert.IsTrue(Immutable<string>());
}
[TestMethod]
public void Is_custom_immutable()
{
Assert.IsTrue(Immutable<MyImmutableClass>());
}
[TestMethod]
public void Is_custom_mutable()
{
Assert.IsFalse(Immutable<MyMutableClass>());
}
[TestMethod]
public void Is_custom_deep_mutable()
{
Assert.IsFalse(Immutable<MyDeepMutableClass>());
}
[TestMethod]
public void Is_custom_deep_immutable()
{
Assert.IsTrue(Immutable<MyDeepImmutableClass>());
}
[TestMethod]
public void Is_propertied_class_mutable()
{
Assert.IsFalse(Immutable<MyMutableClassWithProperty>());
}
private static bool Immutable<T>()
{
return Immutable(typeof(T));
}
private static bool Immutable(Type type)
{
if (type.IsPrimitive) return true;
if (type == typeof(string)) return true;
var fieldInfos = type.GetFields(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance);
var isShallowImmutable = fieldInfos.All(f => f.IsInitOnly);
if (!isShallowImmutable) return false;
var isDeepImmutable = fieldInfos.All(f => Immutable(f.FieldType));
return isDeepImmutable;
}
}
public class MyMutableClass
{
private string _field;
}
public class MyImmutableClass
{
private readonly string _field;
}
public class MyDeepMutableClass
{
private readonly MyMutableClass _field;
}
public class MyDeepImmutableClass
{
private readonly MyImmutableClass _field;
}
public class MyMutableClassWithProperty
{
public string Prop { get; set; }
}
}
You could check that the class is sealed, and using reflection check that each field is read-only (using FieldInfo.IsInitOnly
).
Of course, that only ensures shallow immutability - it wouldn't stop someone from putting a List<int>
field in there, and then changing the contents of the list.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With