-2

I finally got tired of IEnumerable not having an Add method, and decided to add my own through an extension method. My initial attempt was something along these lines:

public static void Add(this IEnumerable<T> items, T item)
{
    ...
}

This threw the expected compiler error about T not being defined, so I changed the signature to Add<T> to define it. (For explanation, see this answer.)

However, this got me thinking. If we create a generic class on our own (like IEnumerable<T>), we are able to add methods to it like the one I had originally tried because T is defined in the class.

I understand that extension methods are not created as a part of the class. There is no "magic" that happens in the compiler to add them to the original class.

I would still think that because of the this declaration on the initial parameter, that the <T> in that parameter could be used to define the type for the method.

My question is this:

Why is there that limitation when it comes to extension methods? Is there something that has been explained for this limitation? Is it something that could be proposed to the language team and added in a future release?

To be more specific, Jonsey reiterated my point a bit more eloquently:

I think I get what you're asking. Why is the compiler not smart enough to recognize, given the method signature, that T is already declared, and doesn't really need to be in the signature?

EDIT

I should have used my new method (Add<T>) before posting as I discovered that when using the method, I don't have to call it generically, I can just use .Add(). I guess that goes along with this answer. I still just find it odd the way it has to be declared, and perhaps that adds a twist to the entire situation.

Argument against duplicate of this question The mentioning of creating IEnumerable<T>.Add() is for illustrative purposes only of the reasoning behind me finding this "peculiarity", and my question is more generic, not specific to that one method.

Community
  • 1
  • 1
krillgar
  • 12,596
  • 6
  • 50
  • 86
  • 2
    I don't understand the question - you can add extension methods to generic types, you just need to abstract over the relevent type parameters as you did for `IEnumerable`. – Lee May 01 '15 at 12:34
  • Why not use IList or ICollection? IEnumerable is to enumerate a collection, not add to it... –  May 01 '15 at 12:35
  • I'm just curious why it is required to declare the method in a generic way when the type is bound to the object it's being operated on in the `this` parameter. Perhaps over the weekend I'll find the time to dive into the Roslyn source to see why, though I don't know how much documentation I could find in there. – krillgar May 01 '15 at 12:36
  • I doubt they would add this capability since making a method generic is pretty easy and this could lead to confusion and unneeded complexity in the compiler. Note generics are defined at the class/interface or method level by design, where as you are talking about defining them in the parameters of a method. – juharr May 01 '15 at 12:36
  • 1
    *IEnumerable* is an interface, not a class – xanatos May 01 '15 at 12:37
  • @ravingheaven That was my original thought, going back and changing those properties to `ICollection` instead of `IEnumerable`. However, then I had a bunch of methods that return `IEnumerable` blow up as it can't be assigned to an `ICollection`. – krillgar May 01 '15 at 12:37
  • possible duplicate of [Why doesn't IEnumerable implement Add(T)?](http://stackoverflow.com/questions/3582317/why-doesnt-ienumerablet-implement-addt) – Arcturus May 01 '15 at 12:37
  • @xanatos I understand the difference between the two. However, methods are declared similarly between the two. – krillgar May 01 '15 at 12:38
  • @Arcturus Absolutely not. I was using that as the example that drove me to this, and not the sole reason for this. – krillgar May 01 '15 at 12:38
  • 1
    I think I get what you're asking. Why is the compiler not smart enough to recognize, given the method signature, that `T` is already declared, and doesn't *really* need to be in the signature? – Jonesopolis May 01 '15 at 12:39
  • @Jonesy Yeah, that's precisely it. – krillgar May 01 '15 at 12:39

4 Answers4

9

Well, you need to ask yourself what's special about T here. Suppose we were to write:

public static void Add(this IEnumerable<X> items, X item)

... would you expect that to work? If so, consider:

public static void Add(this IEnumerable<Button> items, Button item)

Would you expect that to mean the same thing, i.e. effectively be a generic method taking any sequence, or actually only make sense for an IEnumerable<Button> where Button is the System.Windows.Forms.Button class or whatever an appropriate using directive indicates?

Basically, the compiler needs to look up what T means. That can be:

  • A type parameter in the declaring class
  • A type parameter in the declaring method
  • A normal type lookup

In your case, you're in a non-generic class and a non-generic method, so it falls back to a normal type lookup which fails.

Basically you want this to be a type parameter, so it has to be a generic method (or class, but extension methods aren't allowed in generic classes).

If you don't think that the first example (with X) should work, then presumably you're expecting T to be looked up in the context of the IEnumerable<> type, which makes things even odder, as it means that type parameter names become important from "client" code in a way that they aren't anywhere else in the language. It also makes other things tricky. For example:

static void Add(this IEnumerable<T> items, Dictionary<T, T> dict)

Here T is valid for IEnumerable<T>, but Dictionary<,> has TKey, TValue... so should the compiler just say that IEnumerable<T> wins? It ends up being much more confusing than just saying "No, if you want a generic method, you have to declare a type parameter."

Jon Skeet
  • 1,421,763
  • 867
  • 9,128
  • 9,194
  • I understand about 75% of what you're saying. I'm thinking my confusion is still just riding on the fact that I might be putting too much emphasis on the fact that the first parameter in an extension method defines the object being operated on. Since every generic type parameter with the same "variable" declaration must be the same kind, I am not quite seeing the confusion. Like in your `IEnumerable`/`IDictionary` example, all 3 of those `T`s must be the same type so I don't see a confusion there. – krillgar May 01 '15 at 12:59
  • @krillgar You seem to think that `T` is somehow special, and that it is always a generic type argument. It's not special. You can create a regular class named `T`. You can use a name other than `T` for a generic type argument (`T` is just a convention). When you use `IEnumerable` in your method it tries to look up what `T` is, and *it can't find a definition for it*. You need to define was `T` should be, and in your case, you want it to be a generic type argument for the method. As Jon says, there's no reasonable way for C# to infer that that is what you wanted without you saying so. – Servy May 01 '15 at 13:04
  • No, I do understand that there is nothing special with `T` specifically, and that it could be `X`, or `GenericParameterOneToDescribeWhatIPutIntoTheEnumerable`. As I said in the question, if you were to declare a generic class, you could create a method that takes a generic parameter without defining it on the method because it is defined on the class level. But if you were to create an extension method on that generic class, I would then need to redefine the type on the extension method even though the `this` variable is the same generic class as the class declaration. – krillgar May 01 '15 at 13:11
  • 1
    @krillgar Just because you have an extension method with the implicit argument being generic doesn't mean the extension method should be generic, just as Jon showed you. It is in fact quite common to have extension methods of generic types where some or all of the type arguments are to specific known types. – Servy May 01 '15 at 13:43
  • 1
    @krillgar: Note that this is just the same if you extend a class - you can't write `public class Foo : IEnumerable`; you have to either make the class generic or provide a real type argument for `IEnumerable<>`. – Jon Skeet May 01 '15 at 14:40
  • Ah, ok. That bridges that gap that I was missing. Thanks, @JonSkeet. – krillgar May 01 '15 at 15:01
3

You can, you just need to tell the compiler what T is since it doesn't understand it in the context of an extension method.

public static void MyAdd<T>(this IList<T> items, T item)
{

}

When you use it, it will infer the type from the item, for example:

List<string> myList = new List<string>();
myList.MyAdd("this is added to the string");

It looks a little strange in Intellisense because it includes the <> after the name, but you don't have to use it, it will infer the type by itself.

As the other comments say, IEnumerable<T> is an interface, not a class, and you can't Add to an IEnumerable, so your original post doesn't make sense to add an "Add" extension method to something thats not really a collection. But if you were using a collection, like IList, then it works just fine.

Intellisense View

Ron Beyer
  • 11,003
  • 1
  • 19
  • 37
  • Yeah, I edited my question when I realized that it is used the way that you explained. Good point there. – krillgar May 01 '15 at 12:47
  • However, I do disagree with "your original post doesn't make sense to add an `Add` extension method to something thats not really a collection". That's the entire point of extension methods: to add functionality that we as individual developers find useful but the people creating the language didn't think people would need. I understand the "enumerable" part in this example, but the same argument could be made for any extension method that is written for any other class or interface. – krillgar May 01 '15 at 12:49
  • `IEnumerable` is defined as *Exposes the enumerator, which supports a simple iteration over a collection of a specified type*. Enumerators just iterate over a collection, so it just doesn't make sense to have an `Add` method to what is basically a pointer to an item in a collection... You add to the collection itself. – Ron Beyer May 01 '15 at 12:53
  • The point of extension methods is to add convenience, not redefine the type or its purpose. Thats what inheritance is for. – Ron Beyer May 01 '15 at 12:53
1

I would still think that because of the this declaration on the initial parameter, that the in that parameter could be used to define the type for the method.

Why?

Let's say you have this method:

public void Foo(int i1, T item)
{
}

should the compiler auto-detect the T and translate it to:

public void Foo<T>(int i1, T item)
{
}

and let's say you have

public void Foo(int i1, T1 item1, T2 item2)
{
}

should the compiler translate it to?

public void Foo<T1, T2>(int i1, T1 item1, T2 item2)
{
}

or to

public void Foo<T2, T1>(int i1, T1 item1, T2 item2)
{
}

Now, with extension methods it is the same problem. There is no reason why the T "this" generic argument should be first...

public static void Foo<T2, T1>(this IEnumerable<T1> enu, T2 somethingelse)
{
}

It is only a convention.

xanatos
  • 109,618
  • 12
  • 197
  • 280
  • I completely disagree and argue that the fact that it is an extension method does change things. If the `this` argument is a generic type, then by the fact that it is generic should be able to define what the `T1` is. In your example, I would expect the method to be `Foo`, as that would be what it would need to be declared on if you wrote your own `IEnumerable` interface. – krillgar May 01 '15 at 12:46
  • 1
    @krillgar: You can argue that you would *like* it to change things, but unless you can point to where in the language specification it *does* change things, you're just talking about an aspiration rather than how the language actually behaves. – Jon Skeet May 01 '15 at 12:49
1

At the moment, all the compiler has to do when it's compiling a static method inside a static class and observes that the first parameter is preceded by this is that it has to emit the ExtensionAttribute on this method. Otherwise, it continues to parse/compile this method in exactly the same way as it would any other (non-extension) static method.

Under your proposal, it has to radically alter it's parsing rules in this one specific place, and it makes the language less consistent. I wouldn't describe it as a limitation, more of a logical consequence of how language features work together.

Damien_The_Unbeliever
  • 234,701
  • 27
  • 340
  • 448