Now I've been givin my last post some thought, and I thought I'd share my conclusions.
It is obvious that Microsoft are aware of the issues around readonly static fields and reference types in Yukon resident code. You only have to look at the SqlDefinition class to see an example of a type whose perfered usage is as a readonly static field initialized in the static constructor. SqlDefinition members are all readonly operations post construction.
Now from the UNSAFE CAS bucket I would expect to be able to do whatever I like. However, it seems to me that if a reference type is used as a static readonly field from code running from the SAFE or EXTERNAL_ACCESS CAS buckets then the Yukon verifier should be able to check that no state is changed of these types. Now obviously there is an issue that all of these types aren't simply flat objects and may contain embedded references to other objects as well so the tree needs to be chased down. In the end all we are looking for is a call to the IL instructions stfld and maybe ldflda (used for getting a managed pointer to a type - think ref params). So it would seem that on loading an assembly into the database it should be possible to check.
So why isn't this check done? I can think of a few reasons
- The Sql Server team is too damn lazy (I'm pretty sure its not this one)
- The Sql Server team hasn't thought of it (again, looking at the design of the API it must have crossed their minds at least)
- They wanted to do it, but they also wanted to ship the product (possibly)
- Its much harder than I'm making out
Its number 4. that, I think, is the real answer and heres why.
Consider the following class:
public class Person
{
static readonly Pet fluffy;
static Person()
{
fluffy = new Pet();
}
public static void StrokePet()
{
fluffy.MakeNoise();
}
}
public class Pet
{
public void MakeNoise()
{
SqlContext.GetPipe().Send("Growl");
}
}
Well this seems fine - what could go wrong here? Let's alter the code a little ...
public class Person
{
static readonly Pet fluffy;
static Person()
{
if( DateTime.Now.Hour > 12 )
fluffy = new Pet();
else
fluffy = new Cat();
}
public static void StrokePet()
{
fluffy.MakeNoise();
}
}
public class Pet
{
public virtual void MakeNoise()
{
SqlContext.GetPipe().Send("Growl");
}
}
public class Cat : Pet
{
int volume;
public override void MakeNoise()
{
string sound = "Purr";
if( volume > 10 )
sound = "PURR!";
SqlContext.GetPipe().Send(sound);
volume++;
}
}
OK, now were in trouble. We don't know whether fluffy is a generic Pet or a Cat, and the verifier can't tell either as it depends on what time of day the static constructor runs. And this is the simplest example - we can enormously compound the issue with interfaces and Activator.CreateInstance. So now we need a list of other exclusions:
- The static field cannot be a non-sealed class
- The static field cannot be an interface
- Maybe we could back off slightly and say the static field type cannot use reflection to create instances late bound
- Maybe we could back off slightly and say the static field type cannot have virtual methods
- ...
The problem is we end up with a bunch of conditions over and above "it must be immutable" because a verifier cannot determine in many cases how the code paths will look.
So in the end, to allow flexibility of how people want to build their object heirarchies having to state theh rule of immutability and not pretend we can offer guarantees beyond that. A pretence that the verifier could 100% guarantee things that in some situations it cannot is asking for a slew of nasty bugs hitting peoples systems. Its far better in that situation to say "hey, there's an issue you need to be aware of here" and letting the developer take responsibility for their own code.