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:
Wrapper
class is sealed.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?
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:
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.
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.