It is possible to lock
on all non struct
types. In layout of each refernce type on the heap there is special field (sync block) that is used to manage lock. The layout is covered in details in How CLR Creates Runtime Object. Excerpt from article is below:
The OBJECTREF does not point to the beginning of the Object Instance but at a DWORD offset (4 bytes). The DWORD is called Object Header and holds an index (a 1-based syncblk number) into a SyncTableEntry table.
Object layout on the heap:
sync block index
pointer to type
fields...
Speculation part: I believe that original guidance was to lock on whatever is convinient, but it was relatively quickly changed to having special "private object for lock" due to ease of getting external code to deadlock your methods. I think there even were classes in Framework that were impelented with locking on publically visible objects...