Search code examples
c#.netgenericsforeach

foreach and generics compiler issue


I used an interface and class similar to following:

public interface IIdentity
{
    int Id { get; set; }
}

public class Identity : IIdentity
{
    public int Id { get; set; }
}

I was creating instances of the Identity class and adding it to List<Identity> (later referred to as instances creation block).

var identities = new List<IIdentity>();
identities.Add( new Identity { Id = 1 } );
identities.Add( new Identity { Id = 2 } );
identities.Add( new Identity { Id = 3 } );

and then using the identities as follows:

foreach ( IIdentity identity in identities )
{
    Console.WriteLine( "Plug-in: {0}", identity.Id.ToString() );
}

Recently I needed to add more data about IIdentity instances without making any modification to IIdentity and Identity. Therefore I added following class:

public class Wrapper<T> where T : class
{
    public T WrappedObject { get; set; }
    public string Name { get; set; }
    public int Order { get; set; }
}

and replaced the instances creation block with following:

var identities = new List<Wrapper<IIdentity>>();
identities.Add( new Wrapper<IIdentity> { WrappedObject = new Identity { Id = 1 }, Name = "John", Order = 1 } );
identities.Add( new Wrapper<IIdentity> { WrappedObject = new Identity { Id = 2 }, Name = "Jane", Order = 2 } );
identities.Add( new Wrapper<IIdentity> { WrappedObject = new Identity { Id = 3 }, Name = "Joe", Order = 3 } );

I was expecting that I would still have to make some modification to foreach block to make the application compile. However, application compiled successfully and threw System.InvalidCastException when it was run.

As it is visible from provided code, Wrapper does not implement IIdentity interface.

However, the compiler was complaining if one of two modifications was made:

  1. Wrapper class is sealed.
  2. Line 1 is replaced with line 2

Line 1:

foreach ( IIdentity identity in identities )

Line 2:

foreach ( var identity in identities )

The question is why did compiler successfully compile application when modification 1 or 2 are not in place?


Solution

  • The compiler sees that the type of identities is List<Wrapper<IIdentity>>, and it can see that Wrapper<T> does not implement IIdentity. However, by itself that's not enough to produce a compile-time error because there might be a derived class like this somewhere:

    class DerivedWrapper<T> : Wrapper<T>, IIdentity { ... }
    

    Instances of DerivedWrapper can legitimately be put inside identities, so the compiler has to try to cast identity to IIdentity at runtime and throw an exception if this fails.

    The two modifications affect this in different ways:

    1. If Wrapper is sealed then the compiler knows there can be no such derived class so the runtime cast can never succeed; helpfully, it promotes this to a compile time error.

    2. If implicit typing with var is used then the compiler resolves var to the static type of identity, which in this instance is Wrapper<IIdentity>. This means that the loop body tries to access the non-existent member Wrapper<IIdentity>.Id member, hence a compile-time error.