Reputation: 15583
I have two methods:
public static int Insert<T>(this System.Data.IDbConnection connection, T param)
public static int Insert<T>(this System.Data.IDbConnection connection, IList<T> param)
When I try something like this:
connection.Insert(new List<Foo>());
the wrong method (first method) is called.
How can I make it work?
Upvotes: 4
Views: 143
Reputation: 52270
This prototype:
public static int Insert<T>(this System.Data.IDbConnection connection, T param)
...will accept pretty much anything as param
because there are zero type restrictions on it. It will accept a Foo
, an IList<Foo>
, a List<Foo>
and everything non-Foo
as well. As such, it overlaps the second prototype, a problem known as convergence.
It is best to avoid the whole mess if at all possible. If you can, define the first prototype more narrowly, like this:
public static int Insert<T>(this IDbConnection connection, T param) where T: Foo
Then it'll only be invoked when T
is a Foo
or a descendant of Foo
. Neither List<>
nor IList<>
descend from Foo
, so that should solve your problem.
Upvotes: 2
Reputation: 7504
I see the following options:
If you don't want to rewrite methods signature, you should help compiler to choose specific overload.
You can either specify generic parameter
connection.Insert<Foo>(new List<Foo>());
or cast to IList<T>
:
connection.Insert((IList<Foo>)new List<Foo>());
If you don't want to think every time you are calling the method, and if you are free to add more overloads - then easiest way is to just add overloads for all possible IList
implementations you are going to use like this:
public static int Insert<T>(this IDbConnection connection, List<T> param)
{
return connection.Insert((IList<T>)param);
}
If you don't want to change public interfaces at all, and still don't want to cast everywhere - you will need to change body of your most generic method to evaluate passed parameter and maybe pass it internally to other overloads, something like:
public static int Insert<T>(this IDbConnection connection, T param)
{
if (typeof(T).GetInterfaces()
.Any(x => x.IsGenericType && x.GetGenericTypeDefinition() == typeof(IList<>)))
{
// method info retrieval should be written more carefully & cached in static var
var method = MethodBase.GetCurrentMethod().DeclaringType.GetMethods()
.Single(m => m.Name == "Insert" && m.GetParameters()
.Select(p => p.ParameterType)
.Any(t => t.IsGenericType && t.GetGenericTypeDefinition() == typeof(IList<>)));
var generic = method.MakeGenericMethod(typeof(T).GenericTypeArguments[0]);
return (int)generic.Invoke(null, new object[] { connection, param });
}
...
}
Ok, if you don't like it all - just do like that guys from .NET team and rename overload to InsertRange
or something like that. In most cases it's most convenient solution if you're designing public library.
Upvotes: 1
Reputation: 24078
You can specify the generic type instead of relying on type inference
connection.Insert<Foo>(new List<Foo>());
Or you can cast your parameter explicitly to match the method's signature to help the compiler a bit.
connection.Insert((IList<Foo>)new List<Foo>());
Upvotes: 1
Reputation: 4595
If there are generic overloads that can be implicitly called the same way, you have to use an explicit call.
This code will call the second overload.
connection.Insert<Foo>(new List<Foo>());
Upvotes: 5