We all know mutable structs are evil in general. I'm also pretty sure that because IEnumerable<T>.GetEnumerator() returns type IEnumerator<T>, the structs are immediately boxed into a reference type, costing more than if they were simply reference types to begin with.
So why, in the BCL generic collections, are all the enumerators mutable structs? Surely there had to have been a good reason. The only thing that occurs to me is that structs can be copied easily, thus preserving the enumerator state at an arbitrary point. But adding a Copy() method to the IEnumerator interface would have been less troublesome, so I don't see this as being a logical justification on its own.
Even if I don't agree with a design decision, I would like to be able to understand the reasoning behind it.
Indeed, it is for performance reasons. The BCL team did a lot of research on this point before deciding to go with what you rightly call out as a suspicious and dangerous practice: the use of a mutable value type.
You ask why this doesn't cause boxing. It's because the C# compiler does not generate code to box stuff to IEnumerable or IEnumerator in a foreach loop if it can avoid it!
When we see
foreach(X x in c)
the first thing we do is check to see if c has a method called GetEnumerator. If it does, then we check to see whether the type it returns has method MoveNext and property current. If it does, then the foreach loop is generated entirely using direct calls to those methods and properties. Only if "the pattern" cannot be matched do we fall back to looking for the interfaces.
This has two desirable effects.
First, if the collection is, say, a collection of ints, but was written before generic types were invented, then it does not take the boxing penalty of boxing the value of Current to object and then unboxing it to int. If Current is a property that returns an int, we just use it.
Second, if the enumerator is a value type then it does not box the enumerator to IEnumerator.
Like I said, the BCL team did a lot of research on this and discovered that the vast majority of the time, the penalty of allocating and deallocating the enumerator was large enough that it was worth making it a value type, even though doing so can cause some crazy bugs.
For example, consider this:
struct MyHandle : IDisposable { ... }
...
using (MyHandle h = whatever)
{
h = somethingElse;
}
You would quite rightly expect the attempt to mutate h to fail, and indeed it does. The compiler detects that you are trying to change the value of something that has a pending disposal, and that doing so might cause the object that needs to be disposed to actually not be disposed.
Now suppose you had:
struct MyHandle : IDisposable { ... }
...
using (MyHandle h = whatever)
{
h.Mutate();
}
What happens here? You might reasonably expect that the compiler would do what it does if h were a readonly field: make a copy, and mutate the copy in order to ensure that the method does not throw away stuff in the value that needs to be disposed.
However, that conflicts with our intuition about what ought to happen here:
using (Enumerator enumtor = whatever)
{
...
enumtor.MoveNext();
...
}
We expect that doing a MoveNext inside a using block will move the enumerator to the next one regardless of whether it is a struct or a ref type.
Unfortunately, the C# compiler today has a bug. If you are in this situation we choose which strategy to follow inconsistently. The behaviour today is:
if the value-typed variable being mutated via a method is a normal local then it is mutated normally
but if it is a hoisted local (because it's a closed-over variable of an anonymous function or in an iterator block) then the local is actually generated as a read-only field, and the gear that ensures that mutations happen on a copy takes over.
Unfortunately the spec provides little guidance on this matter. Clearly something is broken because we're doing it inconsistently, but what the right thing to do is not at all clear.
Struct methods are inlined when type of struct is known at compile time, and calling method via interface is slow, so answer is: because of performance reason.
Related
This question already has answers here:
How does a generic constraint prevent boxing of a value type with an implicitly implemented interface?
(5 answers)
Closed 8 years ago.
In Jon Skeet's book "C# in Depth, Third Edition" I read this
"All of this would be enough to make generics worthwhile, but there are performance improvements, too. First, because the compiler can perform more enforcement, that leaves less to be checked at execution time. Second, the JIT can treat value types in a particularly clever way that manages to eliminate boxing and unboxing in many situations."
Does that mean in some situations generics might cause boxing? If yes, can someone give an example?
Boxing needs to occur when calling virtual methods, so the following will cause first to be boxed:
public static bool Equals<T>(T first, T other) where T : struct
{
return first.Equals(other);
}
bool eq = Equals(1, 2);
Changing the constraint on T can prevent boxing:
public static bool Equals<T>(T first, T other) where T : IEquatable<T>
{
return first.Equals(other);
}
Some methods can operate on either boxed or unboxed structures, while others can operate on structures only if they are boxed. If a method holds a boxed structure, it can pass that to methods which require boxed structures without having to box it. By contrast, if a method holds an unboxed structure, each call to a method which needs a boxed structure will require a separate boxing operation.
Consider:
interface IStooge { whatever };
struct Stooge : IStooge { whatever};
void Moe(Stooge x)
{
Larry(x);
}
void Larry<T>(T x) where T:IStooge
{
for (int i=0; i<1000000; i++)
Curly(x);
}
void Curly(IStooge x)
{ whatever; }
Moe has an unboxed Stooge. Larry can work with either boxed or unboxed things that implement IStooge. Curly only accepts boxed implementations of IStooge.
If Moe passes an unboxed Stooge to Larry, then Larry is going to create a new boxed Stooge, copy the data from x to that, pass a reference to the new object to Curly, and then abandon that new object; it will then repeat that process 999,999 more times, starting each time with a new boxed Stooge.
If either Moe had cast x to IStooge before passing it, or if Larry were (like Curly) a non-generic method that only accepted boxed implementations of of IStooge, then x would have been boxed before the call to Larry. On each pass through the loop, Larry would pass Curly a reference to the already-boxed Stooge, rather than having to create a new boxed instance. The net effect would be that the number of boxing operations required would be reduced enormously by either making Larry non-generic or using it in non-generic fashion.
In cases where generics can eliminate boxing (they usually can), they naturally reduce boxing. In cases where boxing is going to end up being necessary, however, it's often better to do it in an outer scope rather than a nested one. Generics often prevent boxing in outer scopes, which is good in the cases where it can be prevented altogether, but not so good if it ends up moving it from someplace where it could be done once to someplace it will have to be done repeatedly.
I believe Jon is referring to the fact that a new instance of the Generic type gets compiled for each value-type. If you do List, List, List, there will only be one compiled class for it, because they all inherit from object. If you have List, List, and List, there will be a separate compiled type for each one of those, created by the JITter. This is how it avoids the boxing, because it doesn't put it in the List.
To your second question, I don't know that it's guaranteed to have no runtime boxing, but in the general use of the class, it should be eliminated.
Given that mutable structs are generally regarded as evil (e.g., Why are mutable structs “evil”?), are there potential benefits that might have prompted the designers of the .NET framework to make System.Windows.Point & System.Windows.Vector mutable?
I'd like to understand this so I can decide whether it would make sense to make my own similar structs mutable (if ever). It's possible the decision to make Point and Vector mutable was just an error in judgment, but if there was a good reason (e.g., a performance benefit), I'd like to understand what it was.
I know that I've stumbled over the implementation of the Vector.Normalize() method a few times because it, surprise (!), does not return a fresh Vector. It just alters the current vector.
I always think it should work like this:
var vector = new Vector(7, 11);
var normalizedVector = vector.Normalize(); // Bzzz! Won't compile
But it actually works like this:
var vector = new Vector(7, 11);
vector.Normalize(); // This compiles, but now I've overwritten my original vector
...so, it seems like immutability is a good idea simply for avoiding confusion, but again, perhaps it's worth that potential confusion in some cases.
These types are in the System.Windows namespace and are generally used in WPF applications. The XAML markup of an application is a big part of the framework so for a lot of things, they need a way to be expressed using XAML. Unfortunately there's no way to invoke non-parameterless constructors using WPF XAML (but it is possible in loose XAML) so trying to call a constructor with the appropriate arguments to initialize it wouldn't be possible. You can only set the values of the object's properties so naturally, these properties needed to be mutable.
Is this a bad thing? For these types, I'd say no. They are just for holding data, nothing more. If you wanted to get the size a Window wanted to be, you'd access the DesiredSize to get the Size object representing the size it wanted. You're not meant to "change the desired size" by altering the Width or Height properties of the Size object you get, you change the size by providing a new Size object. Looking at it this way is a lot more natural I believe.
If these objects were more complex and did more complicated operations or had state, then yes, you wouldn't want to make these types neither mutable nor structs. However since they're just about as simple and basic as it can get (essentially a POD), structs would be appropriate here.
Such types are mutable because, contrary to what some people might claim, mutable value-type semantics are useful. There are a few places where .net tries to pretend that value types should have the same semantics as reference types. Since mutable value-type semantics are fundamentally different from mutable reference-type semantics, pretending they're the same will cause problems. That doesn't make them "evil", however--it merely shows a flaw in an object model which assumes that acting upon a copy of something will be semantically equivalent to acting upon the original. True if the thing in question is an object reference; generally true--but with exceptions--if it's an immutable structure; false if it's a mutable structure.
One of the beautiful things about structs with exposed fields is that their semantics are readily ascertained by even simple inspection. If one has a Point[100] PointArray, one has 100 distinct instances of Point. If one says PointArray[4].X = 9;, that will change one item of PointArray and no other.
Suppose instead of using struct Point, one had a mutable class PointClass:
class PointClass {public int X; public int Y;};
How many PointClass instances are stored in PointClass[100] PointClassArray? Is there any way to tell? Will the statement PointClass[4].X = 9 affect the value of PointClass[2].X? What about someOtherObject.somePoint.X?
While the .net collections are not well suited to storage of mutable structs, I would nonetheless regard:
Dictionary<string, Point>;
...
Point temp = myDict["George"];
temp.X = 9;
myDict["George"] = temp;
to have relatively clear semantics, at least in the absence of threading issues. While I consider it unfortunate that .net collections don't provide a means by which one could simply say myDict[lookupKey].X = 9; I would still regard the above code as pretty clear and self-explanatory without having to know anything about Point other than the fact that it has a public integer field called X. By contrast, if one had a Dictionary<PointClass>, it would be unclear what one should be expected to do to change the X value associated with "George". Perhaps the PointClass instance associated with George is not used anywhere else, in which case one may simply write the appropriate field. On the other hand, it's also possible that someone else has grabbed a copy of MyDict["George"] for the purpose of capturing the values therein, and isn't expecting that the PointClass object he's grabbed might change.
Some people might think "Point" should be an immutable struct, but the effect of a statement like somePoint.X = 5; can be fully determined knowing only that somePoint is a variable of type Point, which in turn is a struct with a public int field called X. If Point were an immutable struct, one would have to instead say something like somePoint = new Point(5, somePoint.Y);, which would, in addition to being slower, require examining the struct to determine that all of its fields are initialized in the constructor, with X being the first and Y the second. In what sense would that be an improvement over somePoint.X = 5;?
BTW, the biggest 'gotcha' with mutable structs stems from the fact that there's no way for the system to distinguish struct methods which alter 'this' from those which do not. A major shame. The preferred workarounds are either to use functions which return new structs derived from old ones, or else use static functions which accept "ref" struct parameters.
Possibilities:
It seemed like a good idea at the time to someone who didn't consider the use-cases where it would bite people. List<T>.Enumerator is a mutable struct that was used because it seemed like a good idea at the time to take advantage of the micro-opts that would often happen. It's almost the poster-child for mutable structs being "evil" as it's bitten more than a few people. Still, it seemed like a good idea to someone at the time...
They did think of the downsides, but had some use-case known to them where the performance differences went in struct's favour (they don't always) and was considered important.
They didn't consider structs evil. "Evil" is an opinion about down-sides beating up-sides, not a demonstrable fact, and not everyone has to agree with something even if Eric Lippert and Jon Skeet say it. Personally I think they're not evil, they're just misunderstood; but then again, evil is often easier to deal with than misunderstood for a programmer, so that's actually worse... ;) Maybe those involved disagree.
Quick note on the accepted answer: I disagree with a small part of Jeffrey's answer, namely the point that since Delegate had to be a reference type, it follows that all delegates are reference types. (It simply isn't true that a multi-level inheritance chain rules out value types; all enum types, for example, inherit from System.Enum, which in turn inherits from System.ValueType, which inherits from System.Object, all reference types.) However I think the fact that, fundamentally, all delegates in fact inherit not just from Delegate but from MulticastDelegate is the critical realization here. As Raymond points out in a comment to his answer, once you've committed to supporting multiple subscribers, there's really no point in not using a reference type for the delegate itself, given the need for an array somewhere.
See update at bottom.
It has always seemed strange to me that if I do this:
Action foo = obj.Foo;
I am creating a new Action object, every time. I'm sure the cost is minimal, but it involves allocation of memory to later be garbage collected.
Given that delegates are inherently themselves immutable, I wonder why they couldn't be value types? Then a line of code like the one above would incur nothing more than a simple assignment to a memory address on the stack*.
Even considering anonymous functions, it seems (to me) this would work. Consider the following simple example.
Action foo = () => { obj.Foo(); };
In this case foo does constitute a closure, yes. And in many cases, I imagine this does require an actual reference type (such as when local variables are closed over and are modified within the closure). But in some cases, it shouldn't. For instance in the above case, it seems that a type to support the closure could look like this: I take back my original point about this. The below really does need to be a reference type (or: it doesn't need to be, but if it's a struct it's just going to get boxed anyway). So, disregard the below code example. I leave it only to provide context for answers the specfically mention it.
struct CompilerGenerated
{
Obj obj;
public CompilerGenerated(Obj obj)
{
this.obj = obj;
}
public void CallFoo()
{
obj.Foo();
}
}
// ...elsewhere...
// This would not require any long-term memory allocation
// if Action were a value type, since CompilerGenerated
// is also a value type.
Action foo = new CompilerGenerated(obj).CallFoo;
Does this question make sense? As I see it, there are two possible explanations:
Implementing delegates properly as value types would have required additional work/complexity, since support for things like closures that do modify values of local variables would have required compiler-generated reference types anyway.
There are some other reasons why, under the hood, delegates simply can't be implemented as value types.
In the end, I'm not losing any sleep over this; it's just something I've been curious about for a little while.
Update: In response to Ani's comment, I see why the CompilerGenerated type in my above example might as well be a reference type, since if a delegate is going to comprise a function pointer and an object pointer it'll need a reference type anyway (at least for anonymous functions using closures, since even if you introduced an additional generic type parameter—e.g., Action<TCaller>—this wouldn't cover types that can't be named!). However, all this does is kind of make me regret bringing the question of compiler-generated types for closures into the discussion at all! My main question is about delegates, i.e., the thing with the function pointer and the object pointer. It still seems to me that could be a value type.
In other words, even if this...
Action foo = () => { obj.Foo(); };
...requires the creation of one reference type object (to support the closure, and give the delegate something to reference), why does it require the creation of two (the closure-supporting object plus the Action delegate)?
*Yes, yes, implementation detail, I know! All I really mean is short-term memory storage.
The question boils down to this: the CLI (Common Language Infrastructure) specification says that delegates are reference types. Why is this so?
One reason is clearly visible in the .NET Framework today. In the original design, there were two kinds of delegates: normal delegates and "multicast" delegates, which could have more than one target in their invocation list. The MulticastDelegate class inherits from Delegate. Since you can't inherit from a value type, Delegate had to be a reference type.
In the end, all actual delegates ended up being multicast delegates, but at that stage in the process, it was too late to merge the two classes. See this blog post about this exact topic:
We abandoned the distinction between Delegate and MulticastDelegate
towards the end of V1. At that time, it would have been a massive
change to merge the two classes so we didn’t do so. You should
pretend that they are merged and that only MulticastDelegate exists.
In addition, delegates currently have 4-6 fields, all pointers. 16 bytes is usually considered the upper bound where saving memory still wins out over extra copying. A 64-bit MulticastDelegate takes up 48 bytes. Given this, and the fact that they were using inheritance suggests that a class was the natural choice.
There is only one reason that Delegate needs to be a class, but it's a big one: while a delegate could be small enough to allow efficient storage as a value type (8 bytes on 32-bit systems, or 16 bytes on 64-bit systems), there's no way it could be small enough to efficiently guarantee if one thread attempts to write a delegate while another thread attempts to execute it, the latter thread wouldn't end up either invoking the old method on the new target, or the new method on the old target. Allowing such a thing to occur would be a major security hole. Having delegates be reference types avoids this risk.
Actually, even better than having delegates be structure types would be having them be interfaces. Creating a closure requires creating two heap objects: a compiler-generated object to hold any closed-over variables, and a delegate to invoke the proper method on that object. If delegates were interfaces, the object which held the closed-over variables could itself be used as the delegate, with no other object required.
Imagine if delegates were value types.
public delegate void Notify();
void SignalTwice(Notify notify) { notify(); notify(); }
int counter = 0;
Notify handler = () => { counter++; }
SignalTwice(handler);
System.Console.WriteLine(counter); // what should this print?
Per your proposal, this would internally be converted to
struct CompilerGenerated
{
int counter = 0;
public Execute() { ++counter; }
};
Notify handler = new CompilerGenerated();
SignalTwice(handler);
System.Console.WriteLine(counter); // what should this print?
If delegate were a value type, then SignalEvent would get a copy of handler, which means that a brand new CompilerGenerated would be created (a copy of handler) and passed to SignalEvent. SignalTwice would execute the delegate twice, which increments the counter twice in the copy. And then SignalTwice returns, and the function prints 0, because the original was not modified.
Here's an uninformed guess:
If delegates were implemented as value-types, instances would be very expensive to copy around since a delegate-instance is relatively heavy. Perhaps MS felt it would be safer to design them as immutable reference types - copying machine-word sized references to instances are relatively cheap.
A delegate instance needs, at the very least:
An object reference (the "this" reference for the wrapped method if it is an instance method).
A pointer to the wrapped function.
A reference to the object containing the multicast invocation list. Note that a delegate-type should support, by design, multicast using the same delegate type.
Let's assume that value-type delegates were implemented in a similar manner to the current reference-type implementation (this is perhaps somewhat unreasonable; a different design may well have been chosen to keep the size down) to illustrate. Using Reflector, here are the fields required in a delegate instance:
System.Delegate: _methodBase, _methodPtr, _methodPtrAux, _target
System.MulticastDelegate: _invocationCount, _invocationList
If implemented as a struct (no object header), these would add up to 24 bytes on x86 and 48 bytes on x64, which is massive for a struct.
On another note, I want to ask how, in your proposed design, making the CompilerGenerated closure-type a struct helps in any way. Where would the created delegate's object pointer point to? Leaving the closure type instance on the stack without proper escape analysis would be extremely risky business.
I can tell that making delegates as reference types is definitely a bad design choice. They could be value types and still support multi-cast delegates.
Imagine that Delegate is a struct composed of, let's say:
object target;
pointer to the method
It can be a struct, right?
The boxing will only occur if the target is a struct (but the delegate itself will not be boxed).
You may think it will not support MultiCastDelegate, but then we can:
Create a new object that will hold the array of normal delegates.
Return a Delegate (as struct) to that new object, which will implement Invoke iterating over all its values and calling Invoke on them.
So, for normal delegates, that are never going to call two or more handlers, it could work as a struct.
Unfortunately, that is not going to change in .Net.
As a side note, variance does not requires the Delegate to be reference types. The parameters of the delegate should be reference types. After all, if you pass a string were an object is required (for input, not ref or out), then no cast is needed, as string is already an object.
I saw this interesting conversation on the Internet:
Immutable doesn't mean it has to be a value type. And something that
is a value type is not required to be immutable. The two often go
hand-in-hand, but they are not actually the same thing, and there are
in fact counter-examples of each in the .NET Framework (the String
class, for example).
And the answer:
The difference being that while immutable reference types are
reasonably common and perfectly reasonable, making value types mutable
is almost always a bad idea, and can result in some very confusing
behaviour!
Taken from here
So, in my opinion the decision was made by language usability aspects, and not by compiler technological difficulties. I love nullable delegates.
I guess one reason is support for multi cast delegates Multi cast delegates are more complex than simply a few fields indicating target and method.
Another thing that's only possible in this form is delegate variance. This kind of variance requires a reference conversion between the two types.
Interestingly F# defines it's own function pointer type that's similar to delegates, but more lightweight. But I'm not sure if it's a value or reference type.
Correct me if im wrong but while doing a foreach an IEnumerable<T> creates garbage no matter what T is. But I'm wondering if you have a List<T> where T is Entity. Then say there is a derived class in the list like Entity2D. Will it have to create a new enumerator for each derived class? Therefore creating garbage?
Also does having an interface let's say IEntity as T create garbage?
List<T>'s GetEnumerator method actually is quite efficient.
When you loop through the elements of a List<T>, it calls GetEnumerator. This, in turn, generates an internal struct which holds a reference to the original list, an index, and a version ID to track for changes in the list.
However, since a struct is being used, it's really not creating "garbage" that the GC will ever deal with.
As for "create a new enumerator for each derived class" - .NET generics works differently than C++ templates. In .NET, the List<T> class (and it's internal Enumerator<T> struct) is defined one time, and usable for any T. When used, a generic type for that specific type of T is required, but this is only the type information for that newly created type, and quite small in general. This differs from C++ templates, for example, where each type used is created at compile time, and "built in" to the executable.
In .NET, the executable specifies the definition for List<T>, not List<int>, List<Entity2D>, etc...
I think you may be interested in this article which explains why List(T) will not create "garbage", as opposed to Collection(T):
Now, here comes the tricky part. Rumor has it that many of the types in System.Collections.Generic will not allocate an enumerator when using foreach. List's GetEnumerator, for example, returns a struct, which will just sit on the stack. Look for yourself with .NET Reflector, if you don't believe me. To prove to myself that a foreach over a List doesn't cause any heap allocations, I changed entities to be a List, did the exact same foreach loop, and ran the profiler. No enumerator!
[...]
However, there is definitely a caveat to the above. Foreach loops over Lists can still generate garbage. [Casting List to IEnumerable] Even though we're still doing a foreach over a List, when the list is cast to an interface, the value type enumerator must be boxed, and placed on the heap.
An interesting note: as Reed Copsey pointed out, the List<T>.Enumerator type is actually a struct. This is both good and horrible.
It's good in the sense that calling foreach on a List<T> actually doesn't create garbage, as no new reference type objects are allocated for the garbage collector to worry about.
It's horrible in the sense that suddenly the return value of GetEnumerator is a value type, against almost every .NET developer's intuition (it is generally expected that GetEnumerator will return a non-descript IEnumerator<T>, as this is what is guaranteed by the IEnumerable<T> contract; List<T> gets around this by explicitly implementing IEnumerable<T>.GetEnumerator and publicly exposing a more specific implementation of IEnumerator<T> which happens to be a value type).
So any code that, for example, passes a List<T>.Enumerator to a separate method which in turn calls MoveNext on that enumerator object, faces the potential issue of an infinite loop. Like this:
int CountListMembers<T>(List<T> list)
{
using (var e = list.GetEnumerator())
{
int count = 0;
while (IncrementEnumerator(e, ref count)) { }
return count;
}
}
bool IncrementEnumerator<T>(IEnumerator<T> enumerator, ref int count)
{
if (enumerator.MoveNext())
{
++count;
return true;
}
return false;
}
The above code is very stupid; it's only meant as a trivial example of one scenario in which the return value of List<T>.GetEnumerator can cause highly unintuitive (and potentially disastrous) behavior.
But as I said, it's still kind of good in that it doesn't create garbage ;)
Regardless of whether it's a List<Entity>, List<Entity2D>, or List<IEntity>, GetEnumerator will be called once per foreach. Further, it is irrelevant whether e.g. List<Entity> contains instances of Entity2D. An IEnumerable<T>'s implementation of GetEnumerator may create reference objects which will be collected. As Reed noted, List<T> in MS .NET avoids this by using only value types.
The class List<T> implements IEnumerator<T> explicitly, so that calling GetEnumerator on a variable of type List<T> will cause it to return a List<T>.Enumerator, which has value-type semantics, whereas calling it on a variable of type IEnumerator<T> which holds a reference to a List<T> will cause it to return a value of type IEnumerator<T>, which will have reference semantics.
For the following code I get a compile time error, *
'int' is not a reference type as
required by the lock statement
int i = 0;
lock(i);
But no errors for this:
int i = 0;
Monitor.Enter(i);
I understand that a value type shouldn't be used for locking due to the complications arising due to boxing. But, then why does it work with Monitor.
The reason why is that lock is a language construct and compiler chooses to impose extra semantics on the expression. Monitor.Enter is simply a method call and the C# compiler does not special case the call in any way and hence it goes through normal overload resolution and boxing.
You should definitely not use Monitor.Enter on an int. The reason it works is because the int is boxed, so unless you store a reference to the boxed value, you will be locking on a temporary object, which means that you cannot call Monitor.Exit without getting an exception.
The recommended way to do locking is to create a private readonly object and lock on that. For a static method you can use a private static object.
The specification for the compiler defines the behaviour of lock like so:
The compile time type of the expression of a lock statement shall be a reference-type or a > type parameter (§25.1.1) known to be a reference type. It is a compile-time error for the
compile time type of the expression to denote a value-type.
It then defines what it is equivalent to so long as it compiles
Since Monitor.Exit is just a method call without any constraints it will not prevent the compiler automatically boxing the int and going on its merry (and very) wrong way.
lock is NOT simply syntactic sugar in the same way foreach is not simply syntactic sugar. The resulting IL transform is not presented to the rest of the code as if that was what had been written.
In foreach it is illegal to modify the iteration variable (despite there being nothing at the IL level in the resulting output code that would prevent this). In lock the compiler prevents compile time known value types, again despite the resulting IL not caring about this.
As an aside:
In theory the compiler could be 'blessed' with intimate knowledge of this (and other) methods so that it spotted obvious cases of this happening but fundamentally it is impossible to always spot this at compile time (consider an object passed in from another method, assembly or via reflection) so bothering to spot any such instances would likely be counter productive.
The more the compiler knows about the internals of the API the more problems you will have if you wish to alter the API in future.
It is possible for example that an overload of Monitor.Enter() could be added which took an int and locked on a process wide mutex associated with the int value.
This would conform to the specifications of monitor (even though it would likely be hideous) but would cause massive problems for the older compiler still merrily preventing an operation which had become legal.
Just out of curiosity, what are you doing with the variable 'i' that requires it to be locked? It may be more efficient to use the Interlocked class if all your doing is an increment or something:
Interlocked.Increment(i); // i++ in a thread safe manner
The Interlocked class is the lightest weight thread synch tool that .NET provides, and for simple increments, decrements, reads, or exchanges, it is the best option.
If you are trying to synchronize a block of behavior, then I would simply create an object that can be used as a synchronization root:
object syncRoot = new object();
// ...
lock(syncRoot)
{
// put synced behavior here
}
I'd say it's because Monitor.Enter() is a method call, so the compiler performs the boxing automatically, while lock() is a syntactic element, so the compiler can check and throw an error on value types.