Written by 10:19 Basics of C#, Languages & Coding

С# Nuances: foreach

You may have been asked a question on a job interview: “What needs to be done to make your class work with the foreach loop?” Well, the answer is “To implement IEnumerable”. The answer is correct but not complete.

In very deed, foreach uses the Duck typing. Therefore, it is enough to have the GetEnumerator method that returns “something” that has the MoveNext method and the Current property. It is not necessarily to remember these methods. If you pass an incorrect class into the foreach loop, the compiler will let you know what is missing.

Here is an example

class Program
{
    static void Main(string[] args)
    {
        var container = new Container();
        foreach (var item in container) { }
    }
}

Incorrect container

public class Container { }

The compiler error will be as follow: foreach statement cannot operate on variables of type ‘Container’ because ‘Container’ does not contain a public definition for ‘GetEnumerator’

Let’s add the GetEnumerator method to the container, and also add Enumerator.

Correct container

public class Container
{
    public Enumerator GetEnumerator()
    {
        return new Enumerator();
    }
}

Incorrect Enumerator

public class Enumerator { }

Now the compiler error will be as follow: foreach requires that the return type ‘Enumerator’ of ‘Container.GetEnumerator()’ must have a suitable public MoveNext method and public Current property.

Let’s add the MoveNext method and the Current property into Enumerator.

Correct Enumerator

public class Enumerator
{
    public bool MoveNext()
    {
        return false;
    }

    public object Current
    {
        get { return null; }
    }
}

Now compiler is OK! The Current property can return any type, either the ref type or the value type. Actually, this was the reason for using the Duck typing, in the times when there were no generics, to avoid unnecessary boxing and unboxing.

Another question that you may face is a question about IDisposable. In addition to the common questions about manual resource management, there may be a question about in which cases the compiler can automatically call the Dispose method. Hopefully, we all know the correct answer. Dispose is called automatically when you use the using statement (). However, this is not the whole story. The Dispose method can also be called inside the foreach loop for Enumerator, in case the Enumerator implements IDisposable.

Enumerator with Dispose

public class Enumerator : IDisposable
{
    public bool MoveNext()
    {
        return false;
    }
    public object Current
    {
        get
        {
            return null;
        }
    }
    public void Dispose()
    {
        Console.WriteLine("Dispose");
    }
}

We will see the Dispose line in the console.

For this particular case, the compiler generates the following code

Container container = new Container();
Enumerator enumerator = container.GetEnumerator();
try
    {
        while (enumerator.MoveNext())
         {
            var element = enumerator.Current;
         } 
    }

finally
{
 IDisposable disposable = enumerator as IDisposable;
 if (disposable != null) disposable.Dispose();
}

We will see the Dispose line in the console.

For this particular case, the compiler generates the following code

Container container = new Container();
Enumerator enumerator = container.GetEnumerator();
try
{
    while (enumerator.MoveNext())
    {
    var element = enumerator.Current;
    }
}

finally
{
 IDisposable disposable = enumerator as IDisposable;
 if (disposable != null) disposable.Dispose();
}

Another nuance is that old compilers would generate the following code.

Container container = new Container();
Enumerator enumerator = container.GetEnumerator();
try {
 object element;
while (enumerator.MoveNext())
        {
            element = enumerator.Current;
        }
    }
finally
    {
    IDisposable disposable = enumerator as IDisposable;
if (disposable != null) disposable.Dispose();
    }

The loop variable is declared in another place.

Tags: Last modified: October 06, 2022
Close