When you compile this code:
IEnumerable<string> Test() {
lock (someObj) {
yield return "a";
yield return "b";
}
}
Then the C# compiler creates an enumerator class that calls Monitor.Enter on the first MoveNext call; and calls Monitor.Exit in the third MoveNext call, or in the Dispose method if Dispose is called between after the first MoveNext call and before the third. foreach will automatically dispose the enumerator, but I've seen people write code like
IEnumerable<MyType> myCollection = ...;
if (e.GetEnumerator.MoveNext()) { // if the collection is not empty
So yes, using Monitor.Exit in the Dispose function is the way locking is meant to happen in enumerators, but make sure that your team is aware of the fact that enumerators must be disposed!