4

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;
    
nawfal
  • 70,104
  • 56
  • 326
  • 368

2 Answers2

5

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.

Peter Mortensen
  • 30,738
  • 21
  • 105
  • 131
Tigran
  • 61,654
  • 8
  • 86
  • 123
  • the first looks better, taken. Do you know about the first question of mine? – nawfal Dec 05 '12 at 18:31
  • 1
    A better question is why it cannot infer that the type is ushort based on source? – juharr Dec 05 '12 at 18:33
  • @juharr exactly, but going the old way, it infers! only when extending the method, it doesnt! – nawfal Dec 05 '12 at 18:35
  • 1
    I would `guess` that the first parameter (the `this` one) fixes the generic type to be ushort. After that, `(1, 2, 3, 4, 5)` are treated as integers, that can't be auto-converted to ushort because of possible loss of precission. I guess we need Eric Lippert or Jon Skeet to answer this one. – Martijn Dec 05 '12 at 18:35
  • @juharr: because the funcition is called for `T`. What is a `T` ? You have to specify it. – Tigran Dec 05 '12 at 18:35
  • @Tigran isn't that specified in the extension method approach too? isn't variable `p` already `ushort` when calling `p.In(...)` ? Your answer on my first question is incomplete unless you say what's the difference between the two approaches for the compiler. I rate it as a bug in extension method feature! – nawfal Dec 05 '12 at 18:37
  • 1
    @Martijn then why does Extension.In(p,1,2,3,4,5) work? Would love to see what Eric or Jon would say. – juharr Dec 05 '12 at 18:38
  • @Tigran, but p.In(1,2,3,4,5) is just syntactic sugar for Extention.In(p,1,2,3,4,5). I guess it infers before it applies that? – juharr Dec 05 '12 at 18:40
  • @juharr: the numbers are threated like `integers` per default, so if you pass a type, like `ushort` compiler **has no guarantee** that there would be no any data lost. So comiler **forces** you define concrete *smaller* type, in this case. Everything is correct. – Tigran Dec 05 '12 at 18:46
  • @nawfal: May be, but in that way it will let you jum easily in problems. The way MS tends to construct compiler is *guide* you throw correct process, and not do smart automatic inferences based on bilion possible variants can be found in the code. – Tigran Dec 05 '12 at 18:58
  • @Tigran no, if data loss could be a problem, then it has to be in the second case too. How can compiler tell us that there is a potential pitfall in the first example alone? I think its about the order in which the parameters are inferred and in the first case the `params T[]` is first inferred I guess. – nawfal Dec 05 '12 at 19:01
1

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);
}
Quartermeister
  • 57,579
  • 7
  • 124
  • 111
  • Accepted! `An implicit identity, reference or boxing conversion exists from expr to the type of the first parameter of Mj.` gives the answer to this! Though I believe C# could be a lot smarter! :( – nawfal Dec 06 '12 at 09:52