Thoughts on Immutable Types
If you use a C# struct, it should be immutable. Mutable structs cause all kinds of nasty problems; don’t do it. If I had to design the C# language over, I’d say that mutable structs would have to be marked with the ‘mutable’ keyword.
If you want to follow the pattern of making your stucts immutable, here’s one way to do it.
- Create an ImmutableAttribute:
[AttributeUsage(AttributeTargets.Struct | AttributeTargets.Class, Inherited = false, AllowMultiple = false)]
sealed class ImmutableAttribute : Attribute { }
- Put it on your struct:
[Immutable]
struct S
{
readonly int x;
}
- Write an FxCop rule that verifies immutability.
What does the FxCop rule look for?
All fields must be marked ‘readonly’.
Fields must be either a built-in type or must have the Immutable attribute.
What do you think? Does my scheme seem workable? Is the result valuable?
Edit1: From a comment: see also ValueObject on the wiki.
Edit2: From a comment: for this to work, you need to be able to place the attribute on classes as well.
Comments
- Anonymous
June 10, 2004
The comment has been removed - Anonymous
June 10, 2004
I think it will work, however I would permit ImmutableAttribute to be applied to structures and regular classes as it is possible and potentially desireable to write immutable reference types, just harder. However, if you can verify immutability on a struct you can on a class as well.
There is an issue with inheritance of classes which may make it ideal to only allow structs to have fields of types marked with Immutable AND that are sealed. - Anonymous
June 10, 2004
Sound like <a href="http://c2.com/cgi/wiki?ValueObject">ValueObject</a> to me. - Anonymous
June 10, 2004
Okay so that link didn't work
http://c2.com/cgi/wiki?ValueObject - Anonymous
June 10, 2004
Thanks Jim & Daniel. I've updated the post with your ideas.
Tony: See also http://c2.com/cgi/wiki?ValueObjectsShouldBeImmutable - Anonymous
June 10, 2004
Daniel: Can you explain in more detail why you think sealing is important? - Anonymous
June 10, 2004
Daniel: Can you explain in more detail why you think sealing is important? - Anonymous
June 10, 2004
Jay: Pretty simple. If a class isn't sealed you run this risk:
public class C
{
public readonly int x;
}
public class D : C
{
public string y;
}
While it doesn't allow x to be changed, y can be which might change behaviour and break immutability(GetHashCode could change, etc), IMHO. Because of that, one would want to seal the class(or seal all underlying methods).
Basically, when I consider something to be immutable, I would want C where C.x = 10 to behave identically to D where D.x = 10 when using C's interface. D's interface should be irrelevent here.
It may not be directly related to immutability, but it is an expectation that I think is applicable. - Anonymous
June 10, 2004
But doesn't http://c2.com/cgi/wiki?ValueObjectsCanBeMutable show that mutable structs are fine, as long as they are by-value (as they are in C#)? - Anonymous
June 10, 2004
Jay: The issue i have is that while this gives you immutability it can only do so with your own types since you don't have the power to mark up the framework. For example:
[Immutable]
class C {
readonly SomeTypeYouDontOwn stydo;
readonly JayzType jt;
[Immutable]
public void ReadMe() {
jt.DoSomething();
stydo.DoSomething();
}
}
Now, JayzType you own and you can Ensure that DoSomething() wont' have any effect (which can be checked by FxCop). This is simple since the rule on C would ensure that jt was an immutable type, which means that none of its methods could affect it. However, for stydo, you're SOL.
Does this mean that you can only have immutable types if you never use the BCL?
I've followed this pattern with new classes I've written (including the same naming of the ImmutableAttribute) however, it puts me in a very NIH mindset where i have to end up redoing a lot of preexisting work. - Anonymous
June 10, 2004
Jay: Inherited shouldn't be set to 'false'. If this were the case then the fx cop rules couldn't be enforced. You might have:
[Immutable]
class C {
readonly int i;
public virtual void DoSomething() { }
}
class D : C {
int j;
public override void DoSomething() {
j++;
}
}
[Immutable]
class E {
readonly C c = new D();
public void Blah() {
c.DoSomething();
}
}
You just violated c's immutability. - Anonymous
June 10, 2004
Cyrus: If this isn't built in to the Frameworks, the result is that you'll get an FxCop warning like "Immutable type 'T' has mutable field 'f'". Then you'll look at 'f' and see that it's an FX type you can't control.
Not great, but it's the best we can do, I think.
I'll fix the Inherited setting. - Anonymous
June 10, 2004
I don't quite follow. How does a mutable struct get me into trouble in my C# code? I'm very curious after reading your post and the vague explaination by Fowler. Would you mind posting an example? Thanks! - Anonymous
June 10, 2004
Sean: see http://blogs.msdn.com/jaybaz_MS/archive/2004/06/04/148968.aspx for one place that mutable structs are a problem. (A problem for me more than for you!) - Anonymous
June 10, 2004
Hmm, this sounds vaguely like const discussions I have had in C/C++ days.
System.String is essentially an immutable type, but it doesn't have readonly on it's properties. Are you arguing that you would like that? (Not trying to be condescending, but simply questioning.) - Anonymous
June 10, 2004
I would actually like to see this same thing for "Exceptions". Now that would be cool.
[Throws(typeof(CustomException))]
[Throws(typeof(SqlException))]
[Throws(typeof(ArgumentNullException))]
... :-p wonder how much FxCop code would be required to find out if callers to that class actually handle or define ThrowsAttributes for it. - Anonymous
June 11, 2004
Adam: Checked exceptions are something we've looked at for C#. When we ask Java users about them, we heard many say they hated the feature, and just marked everything as returning Exception.
However, instead of adding it to the language, it may be practical to use a static analysis tool. I know Microsoft Research has been exploring this area, but last I heard they're far from having a shipable product.
There is a lot of confusion (or at least disagreement) about how exceptions should be used in programming. I see that fact as interfering with innovation & development in tools for exceptions. - Anonymous
June 11, 2004
Jim: 'string' is the example of an immutable reference type that I'm trying to follow. The CLR can do all kinds of good optimizations with a 'string'. What could it do if other types could be immutable?
The real difference between C++ 'const' methods and immutable types is that only the latter can be enforced. It's always possible to work around C++ 'const', which turns it into "productivity tool + developer documentation". - Anonymous
June 11, 2004
My const comments came because of Cyrus's statement about not being able to mark up the framework.
There were similar problems with "const"ness and frameworks as well. - Anonymous
June 11, 2004
It sounds like you are saying that the CLR does these optimizations based upon the type and special casing?
Hmm, I guess I wouldn't have expected that. An attribute such as you are suggesting would surprise me less. - Anonymous
June 11, 2004
Yes, there is alot of issue with const. One of the largest issues with adding const semantics to a CLR language is the BCL doesn't support it. Limiting immutability to a type instead of an instance(as const does) is a bit easier to deal with without the BCL however.
A guarentee that a struct is immutable means that struct is immutable, it doesn't mean that other code can't change it becuase the Immutable flag says no, but because there is no way to do it. As such, the BCL doesn't need to support the concept quite as much(a more advanced static checker would ideally be able to analyze types and decide which are mutable and which aren't, anyway). The difference is if there is no immutable Point, then there is no immutable Point, however there will always be a const Point if const exists. - Anonymous
July 01, 2004
I have an FxCop rule like this on my projects. I also have an attribute that AppliesTo fields and marks them as cache fields. This allows the immutable type to cache long computations if neccessary. The FxCop rule that attempts to verify this is intentionally strict, and requires the field to be private, and assigned in only one place.
This check will work better in C# 2.0, where my new implementation against beta 1 uses the same attribute but requires:
a) field is private
b) field has a reference type, or is of type Nullable<T>
c) field can only be assigned through static void AssignCache<T>(ref T field, T val) which checks to see if the field is null before making the assignment, and throws an exception on reassignment
What do you think?