Why can 'int' be treated as 'ushort' but not when passed as a parameter in an extension method and what's an elegant solution to it?

StackOverflow https://stackoverflow.com/questions/13729869

Question

I have this extension method:

public static bool In<T>(this T source, params T[] list)
{
    return list.Contains(source);
}

Now I need to use the above method for a ushort. When I try

ushort p = 3;
if (p.In(1, 2, 3, 4, 5))
   return;

The first line casts 3 to a ushort well. But when 3 is passed as a parameter, I get the error

'ushort' does not contain a definition for 'In' and the best extension method overload 'Extensions.In(T, params T[])' has some invalid arguments.

But this works:

ushort p = 3;
if (Extensions.In(p, 1, 2, 3, 4, 5))
   return;

which is weird.

  1. Why does it work with the second example, but not the first?

  2. What's a good alternative that would help me here? Since there are no literals for short or ushort I'm failing to find a simpler alternative than manually casting each integer like this:

    ushort p = 3;
    if (p.In((ushort)1, (ushort)2, (ushort)3, (ushort)4, (ushort)5))
       return;
    
Was it helpful?

Solution 2

Why does it work with second example, but not first?

First, let's work out what the compiler infers T to be. Some parameters are ushort and some are int. ushort has an implicit conversion to int and int does not have an implicit conversion to ushort, so T is int.

The key here is in section 7.6.5.2 of the C# 4 specification (emphasis mine):

An extension method Ci.Mj is eligible if:

  • Ci is a non-generic, non-nested class
  • The name of Mj is identifier
  • Mj is accessible and applicable when applied to the arguments as a static method as shown above
  • An implicit identity, reference or boxing conversion exists from expr to the type of the first parameter of Mj.

There is an implicit conversion from ushort to int, but not an identity, reference, or boxing conversion!

So, the following are legal:

Extensions.In<ushort>(p, 1, 2, 3, 4, 5);
Extensions.In<int>(p, 1, 2, 3, 4, 5); // implicit conversion allowed
Extensions.In(p, 1, 2, 3, 4, 5); // T is int
p.In<ushort>(1, 2, 3, 4, 5);

but the following are not:

p.In<int>(1, 2, 3, 4, 5); // implicit conversion not allowed
p.In(1, 2, 3, 4, 5); // T is int

Note that you can get the same error without generics if you define In as

public static bool In(this int source, params int[] list)
{
    return list.Contains(source);
}

OTHER TIPS

Well, you define a generic function, so you have to define the exact type it has to deal with. Because if you give just numbers (1, 2, 3, 4, etc.) to the function. They could be just anything: short, ushort, integer...

So you can do:

or

ushort p = 3;
if (p.In<ushort>(1, 2, 3, 4, 5))
   return;

Or like you did in your second example, cast every parameter to the desired type:

ushort p = 3;
if (p.In((ushort)1, (ushort)2, (ushort)3, (ushort)4, (ushort)5))
   return;

I, personally, would prefer the first case.

EDIT

What about why it works in this case:

ushort p = 3;
if (Extensions.In(p, 1, 2, 3, 4, 5))
   return;

is because you explicitly pass like a this (first parameter) the p which is a known type for compiler, so it can infer it.

The default type for 1, 2, 3, 4, etc. is int. So when you call

p.In(1, 2, 3, 4, 5)

T is treated (or tends to be) like integer value. As a compiler has no guarantee that there would not be any data lost (when you're using ushort on integer), it gives an error message. It forces you to explicitly define a smaller type.

Note that: function parameters are defined like T[], so you pass integers (at least compiler thinks so) but pretending it to be ushort (as you're calling ext-method from p).

For a proof, try to run

int p = 3;
if (p.In(1, 2, 3, 4, 5))
   return;

This works perfectly.

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top