TypeHelper.TypesAreAssignmentCompatible not up-to-date with C#4.0 variance support?

Topics: Metadata Model
Nov 18, 2010 at 5:02 PM

Given the following types:

    public class Alpha {
    public class Beta : Alpha {

TypeHelper.TypesAreAssignmentCompatible is returning false if I call it with source type IList<Beta> and target type IEnumerable<Alpha>.

On the other hand, if I compile this code with VS2010:

    public class Gamma {
        public IEnumerable<Alpha> Get() {
            var list = new List<Beta>();
            var list2 = (IList<Beta>)list;
            return list2;

I get no error or warning whatsoever (as expected) and no conversion is emitted:

.method public hidebysig instance class [mscorlib]System.Collections.Generic.IEnumerable`1 Get() cil managed
    .maxstack 1
    .locals init (
        [0] class [mscorlib]System.Collections.Generic.List`1 list,
        [1] class [mscorlib]System.Collections.Generic.IList`1 list2,
        [2] class [mscorlib]System.Collections.Generic.IEnumerable`1 CS$1$0000)
    L_0000: nop 
    L_0001: newobj instance void [mscorlib]System.Collections.Generic.List`1::.ctor()
    L_0006: stloc.0 
    L_0007: ldloc.0 
    L_0008: stloc.1 
    L_0009: ldloc.1 
    L_000a: stloc.2 
    L_000b: br.s L_000d
    L_000d: ldloc.2 
    L_000e: ret 

Is there something I am missing or the method is not up-to-date?

Nov 18, 2010 at 5:07 PM

Oh, there is a Type1IsCovariantWithType2() call in TypeHelper.TypesAreAssignmentCompatible()...

I'm following it to see where it leads.

Nov 18, 2010 at 5:12 PM

Ok, Type1IsCovariantWithType2 only deals with IArrayTypeReference. I understand this means C#4.0 variance support is not taken into account currently.

But please correct me if I'm mistaken. Thanks!


Nov 18, 2010 at 10:23 PM
Edited Nov 19, 2010 at 12:56 PM

This method should be an improved version of TypeHelper.TypesAreAssignmentCompatible:

    public static class TypeHelperEx {
        public static bool TypesAreAssignmentCompatible(ITypeDefinition source, ITypeDefinition target) {
            return TypeHelper.TypesAreAssignmentCompatible(source, target) || TypesAreAssignmentCompatible_Internal(source, target);
        static bool TypesAreAssignmentCompatible_Internal(ITypeDefinition source, ITypeDefinition target) {
            if (target.IsInterface) {
                var targetGenericTypeInstance = target as IGenericTypeInstance;
                if (targetGenericTypeInstance != null) {
                    var targetGenericType = targetGenericTypeInstance.GenericType.ResolvedType;

                    foreach (var sourceInterfaceCandidate in EnumerateImplementedInterfaces(source)) {
                        var sourceInterfaceCandidateGenericTypeInstance = sourceInterfaceCandidate as IGenericTypeInstance;
                        if (sourceInterfaceCandidateGenericTypeInstance != null) {
                            if (GenericInterfacesAreAssignmentCompatible(sourceInterfaceCandidateGenericTypeInstance, targetGenericTypeInstance)) {
                                return true;

            return false;

        public static bool GenericInterfacesAreAssignmentCompatible(IGenericTypeInstance source, IGenericTypeInstance target) {
            if (TypeHelper.TypesAreEquivalent(source.GenericType, target.GenericType)) {
                var genericType = source.GenericType.ResolvedType;

                var genericParameterEnumerator = genericType.GenericParameters.GetEnumerator();
                var sourceGenericArgumentEnumerator = source.GenericArguments.GetEnumerator();
                var targetGenericArgumentEnumerator = target.GenericArguments.GetEnumerator();

                while (genericParameterEnumerator.MoveNext()) {
                    if (!sourceGenericArgumentEnumerator.MoveNext()) return false;
                    if (!targetGenericArgumentEnumerator.MoveNext()) return false;

                    var genericParameter = genericParameterEnumerator.Current;
                    var sourceGenericArgument = sourceGenericArgumentEnumerator.Current.ResolvedType;
                    var targetGenericArgument = targetGenericArgumentEnumerator.Current.ResolvedType;

                    if (genericParameter.Variance == TypeParameterVariance.NonVariant) {
                        if (!TypeHelper.TypesAreEquivalent(sourceGenericArgument, targetGenericArgument)) {
                            return false;
                    } else if (genericParameter.Variance == TypeParameterVariance.Covariant) {
                        if (!TypeHelperEx.TypesAreAssignmentCompatible(sourceGenericArgument, targetGenericArgument)) {
                            return false;
                    } else if (genericParameter.Variance == TypeParameterVariance.Contravariant) {
                        if (!TypeHelperEx.TypesAreAssignmentCompatible(targetGenericArgument, sourceGenericArgument)) {
                            return false;
                    } else {
                        throw new NotImplementedException();
                return true;
            return false;

        public static IEnumerable<ITypeDefinition> EnumerateImplementedInterfaces(ITypeDefinition typeDefinition) {
            return EnumerateImplementedInterfaces_Internal(typeDefinition).Distinct();
        static IEnumerable<ITypeDefinition> EnumerateImplementedInterfaces_Internal(ITypeDefinition typeDefinition) {
            if (typeDefinition.IsInterface) {
                yield return typeDefinition;
            foreach (var interfaceDefinition in typeDefinition.Interfaces.Select(x => x.ResolvedType)) {
                yield return interfaceDefinition;
                foreach (var interfaceDefinition2 in EnumerateImplementedInterfaces(interfaceDefinition)) {
                    yield return interfaceDefinition2;
            foreach (var baseClass in typeDefinition.BaseClasses.Select(x => x.ResolvedType)) {
                foreach (var interfaceDefinition3 in EnumerateImplementedInterfaces(baseClass)) {
                    yield return interfaceDefinition3;

Nov 19, 2010 at 12:27 PM
Edited Nov 19, 2010 at 12:58 PM

An earlier version of the code above was not correct. The current version should be better.

Nov 20, 2010 at 8:49 PM

CCI should follow the CLR rules here, which in VS2010 should be the same as the C# rules. I've had a look a the latest draft of the CLR spec and it is quite involved. I'll try to implement it sooner rather than later, but it will take a while for me to understand how to implement the spec efficiently.

Nov 28, 2010 at 8:30 PM

Ok. Thanks.

I posted that method because, in the meantime, that works good enough for my cases and maybe someone else can use it too. It's easy to remove it once you implement it in a better way.