MuriloKunze
MuriloKunze

Reputation: 15583

How to differentiate between T and IList<T>

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

Answers (4)

John Wu
John Wu

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

Lanorkin
Lanorkin

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

dee-see
dee-see

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

Matt Rowland
Matt Rowland

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

Related Questions