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?
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>
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With