Understanding C# Covariance And Contravariance (3) Variances in .NET

Understanding C# Covariance And Conreavariance:

Not many generic types in .NET have variant type parameter(s). LINQ can be uses to query these generic types from .NET libraries.

The following method queries a specified directory, and retrieve all .NET assemblies:

public static partial class ReflectionHelper
{
    public static IEnumerable<Assembly> GetAssemblies(string directory)
    {
        return Directory.EnumerateFiles(directory, "*.dll")
            .Select(file =>
                {
                    try
                    {
                        return Assembly.LoadFrom(file);
                    }
                    catch (BadImageFormatException)
                    {
                        return null;
                    }
                })
            .Where(assembly => assembly != null);
    }
}

The following method queries one specified assembly, and filter generic types with any variant type parameter:

public static partial class ReflectionHelper
{
    public static IEnumerable<Type> GetTypesWithVariance(Assembly assembly)
    {
        try
        {
            return assembly.ExportedTypes.Where(type =>
                type.IsGenericTypeDefinition && type.GetGenericArguments().Any(argument =>
                    (argument.GenericParameterAttributes & GenericParameterAttributes.Covariant)
                    == GenericParameterAttributes.Covariant
                    ||
                    (argument.GenericParameterAttributes & GenericParameterAttributes.Contravariant)
                    == GenericParameterAttributes.Contravariant));
        }
        catch (TypeLoadException)
        {
            return Enumerable.Empty<Type>();
        }
    }
}

The last method queries the assemblies in the same directory of mscorlib.dll, and retrieves the wanted types, and orders them by name:

public static partial class ReflectionHelper
{
    public static IEnumerable<Type> GetTypesWithVariance()
    {
        string mscorlibPath = typeof(object).Assembly.GetName().CodeBase;
        string directory = Path.GetDirectoryName(new Uri(mscorlibPath).AbsolutePath);
        return GetAssemblies(directory)
            .SelectMany(GetTypesWithVariance)
            .OrderBy(type => type.Name);
    }
}

Here is the result of executing the last method:

  • System namespace:
    • Action`1 to Action`16, Func`1 to Func`17
    • Comparison<T>
    • Converter`2
    • IComparable<T>,
    • IObservable<T>, IObserver<T>
    • IProgress<T>
    • Predicate<T>
  • System.Collections.Generic namespace:
    • IComparer<T>, IEqualityComparer<T>
    • IEnumerable<T>, IEnumerator<T>
    • IReadOnlyCollection<T>, IReadOnlyList<T>
  • System.Linq namespace:
    • IGrouping`2
    • IOrderedQueryable<T>, IQueryable<T>

MSDN has a List of Variant Generic Interface and Delegate Types, but it is inaccurate. For example, it says TElement is covariant for IOrderedEnumerable<TElement>, but actually not:

namespace System.Linq
{
    public interface IOrderedEnumerable<TElement> : IEnumerable<TElement>, IEnumerable
    {
        IOrderedEnumerable<TElement> CreateOrderedEnumerable<TKey>(Func<TElement, TKey> keySelector, IComparer<TKey> comparer, bool descending);
    }
}

No Comments