Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

C# extension method compilation / compatibility checks failing based on order of namespaces

I am writing two extension methods. One to work on a single object, and another to work on a collection of objects. When calling the extension method, the C# compiler seems to get confused as to which one to use, and fails compilation.

More surprisingly, if I move the extension methods to different namespaces, even if I include both namespaces in the callsite, compilation only fails if the namespaces are in a particular order alphabetically - switching the namespaces causes compilation to succeed.

Here is the code:

public static class DBObjectExtensions
{
    public static void PopulateRelations<T>(this T obj, params RelationToPrefetch[] relationsToPrefetch) where T : IDBObject
    {
        if (obj == null)
        {
            return;
        }

        obj.Transaction.PopulateRelations<T>(new[]{ obj }, relationsToPrefetch);
    }

    public static void PopulateRelations<T>(this IEnumerable<T> objects, params RelationToPrefetch[] relationsToPrefetch) where T : IDBObject
    {
        var first = objects.FirstOrDefault();
        if (first == null)
        {
            return;
        }

        first.Transaction.PopulateRelations<T>(objects, relationsToPrefetch);
    }
}

This is the callsite line that fails compilation:

List<ITable> list = ... // ITable inherits from IDBObject
list.PopulateRelations(xxx);

Fails with error CS0311:

The type 'System.Collections.Generic.List' cannot be used as type parameter 'T' in the generic type or method 'Granta.MI.DBObjectExtensions.PopulateRelations(T, params Granta.MI.RelationToPrefetch[])'. There is no implicit reference conversion from 'System.Collections.Generic.List' to 'Granta.MI.IDBObject'.

Note that this line succeeds compilation if I delete the 2nd extension method.

Also note that writing trampoline methods (for every possible type of collection...) also works:

public static void PopulateRelations<T>(this List<T> objects, params RelationToPrefetch[] relationsToPrefetch) where T : IDBObject
{
    ((IEnumerable<T>)objects).PopulateRelations(relationsToPrefetch);
}

public static void PopulateRelations<T>(this IList<T> objects, params RelationToPrefetch[] relationsToPrefetch) where T : IDBObject
{
    ((IEnumerable<T>)objects).PopulateRelations(relationsToPrefetch);
}

Why can't the compiler figure out there is a matching extension method? And more confusingly, if I put one of the methods in a different namespace, and I include that namespace, why does compilation then succeed? Is there anything I can do to fix this?

like image 788
Michael Parker Avatar asked Oct 30 '22 18:10

Michael Parker


1 Answers

Generic constraints are not part of the method signature, so the compiler choose PopulateRelations<T>(this T obj, params RelationToPrefetch[] relationsToPrefetch) because T is more derived than IEnumerable<T>.

Example, between these 2 methods:

public static void PopulateRelations(this List<ITable> obj, params RelationToPrefetch[] relationsToPrefetch)
{
    // Do something
}

public static void PopulateRelations(this IEnumerable<ITable> objects, params RelationToPrefetch[] relationsToPrefetch)
{
    // Do something
}

The first one is chosen when calling:

List<ITable> list;
PopulateRelations(list, something); // Not calling as extension method to more clear

Because list matches directly with List<ITable>

like image 196
Matteo Umili Avatar answered Nov 12 '22 20:11

Matteo Umili