Ayende @ Rahien

Refunds available at head office

Generic extension methods

I was playing around with the compiler when I hit this interesting feature. I was very surprised to see that this has compiled successfully.

   1: static class Program
   2: {
   3:     static void Main(string[] args)
   4:     {
   5:         IProcesser<GZipStream> p = null;
   6:         p.HasTimeout();
   7:     }
   8: }
   9:  
  10: public static class Extensions
  11: {
  12:     public static bool HasTimeout<T>(this IProcesser<T> s)
  13:         where T : Stream
  14:     {
  15:         return s.Desitnation.CanTimeout;
  16:     }
  17: }
  18:  
  19: public interface IProcesser<TDestination>
  20:     where TDestination : Stream
  21: {
  22:     TDestination Desitnation { get; }
  23: }

Comments

Andrew
02/24/2008 11:01 PM by
Andrew

Maybe I'm missing something but any reason it shouldn't?

Because it's a nulled variable?

You can do the same with any method on a variable:

    static void Main(string[] args)

    {

        string p = null;

        string[] q = p.Split(',');

    }

Sure it'd fail during execution, but not on compile.

We figured this out a while back, and do a test for a null reference that throws an exception, if it's a critical part of the app.

Ayende Rahien
02/24/2008 11:04 PM by
Ayende Rahien

Andrew,

The extension method is a generic method.

I was surprised that that worked.

Andrew
02/24/2008 11:09 PM by
Andrew

Actually, I did look at the top afterwards, at the name of the post - and yeah... red faced now :)

Yep, we have a couple of generic methods as extension methods too - comes nice and handy when you need to do the same thing in a few different cases.

We're starting to build up some core extension methods to be shared between specific projects, and each project has it's own extension method library - but we do like having some generics used on the global ones that can work between a couple of project idiosyncrasies.

Apologies to the assume, it did make an ass out of me, at least :)

Craig Shearer
02/25/2008 12:16 AM by
Craig Shearer

There are two things about this that puzzle me - firstly and superficially, there is a spelling mistake - Desitnation instead of Destination - but it doesn't matter to the compiler.

But, there's no implementation of the getter (TDestination Desitnation {get;}) That is weird!

Oh, and if you're going to show us code, it would be nice if it didn't have line numbers - it makes it terrible to cut and paste.

Ayende Rahien
02/25/2008 12:23 AM by
Ayende Rahien

I am trying to find my previous code highlighter.

As for why it compiles, that is an interface, not a class

VIjay Santhanam
02/25/2008 12:58 AM by
VIjay Santhanam

It seems like one must check for a not null "this" argument as a precondition in extension methods.

Also, the compiler is smarter with generic methods. Notice you don't have to specify the parameters to any generic System.Linq ext methods.

Two typical linq extension methods include

public static IQueryable AsQueryable(this IEnumerable source);

public static IQueryable AsQueryable(this IEnumerable source);

If u have the code

string[] items = {"a","b"."c" ,"two"};

items.ToQueryable();

the second line will use the generic method and not the first signature.

Is this a case of the compiler inferring type when picking call methods?

Thomas Krause
02/25/2008 01:08 AM by
Thomas Krause

Why are you surprised?

Isn't the whole LINQ stuff based on extension methods for the (generic) IEnumerable interface?

alberto
02/25/2008 01:08 AM by
alberto

Well, maybe I am missing the obvious, but I don't see anything strange. It's a static method behind the scenes, so the compiler shouldn't care whether you are passing a null argument to it. I don't see why being a generic method should change that fact.

Ayende Rahien
02/25/2008 01:20 AM by
Ayende Rahien

VIjay ,

Yes, it does.

It is significantly smarter than it was for 2.0

Julian Birch
02/25/2008 01:21 AM by
Julian Birch

Actually, the thing I find most surprising is that the type inference works. I'm pretty sure the pretty similar code you could write for .NET 2.0 doesn't compile.

The whole "extension methods aren't instance methods" is a bit weird, although I have to admit I've already abused it. (i.e. made it not throw an exception)

Ayende Rahien
02/25/2008 01:27 AM by
Ayende Rahien

Thomas,

Methods for generic interface, yes.

Generic methods, I wouldn't have expected that.

Ayende Rahien
02/25/2008 01:27 AM by
Ayende Rahien

Albert,

I am surrised that the compiler can pick it up, actually.

Jimmy Bogard
02/25/2008 04:37 AM by
Jimmy Bogard

Awww old news

http://grabbagoft.blogspot.com/2007/07/constrained-generic-extension-methods.html

It's very interesting indeed. I've been playing around with specification-type extensions, for things like IComparable.

It was very surprising the first time I compiled it. We had a little bet at the office whether it would or not. And an interesting DBC-style implementation too:

http://eltonomicon.blogspot.com/2007/10/static-class-extensibility-via.html

You do still run into the problems of generic constraints, and you have to weigh whether you target the constraint directly or use constraints.

Jon Skeet
02/25/2008 08:36 AM by
Jon Skeet

Ayende,

Even talking about generic methods, LINQ is still stuffed full of examples.

Take Select - yes, it acts on a particular generic interface, but it has to project to another generic interface (a sequence of the result type) and that result type is specified by a type parameter.

Indeed, even simple methods like Where which only act on a generic interface are still generic methods - it's not like the type itself is Enumerable with extension methods for the corresponding IEnumerable. There are relatively few methods in LINQ which aren't generic methods.

Jon

Jimmy Bogard
02/25/2008 12:36 PM by
Jimmy Bogard

Also, not only does it compile successfully, but your generic constraints are correctly interpreted by Intellisense. It's not all IProcessor implementations that catch it, it's only those that conform to the constraint.

Even removing the constraint on the interface, the Intellisense still pops up correctly if you constrain the generic method. I haven't tried R# code completion yet, this is only VS built-in intellisense.

Luke Breuer
02/25/2008 07:00 PM by
Luke Breuer

Ayende, I've been using the following code (which will be mangled by the blog comment engine, grrrr) for a while:

public static string Join(this IEnumerable strings, string delimiter)

{

return strings.Any()

    ? string.Join(delimiter, strings as string[] ?? strings.ToArray())

    : "";

}

I also echo Thomas Krause: LINQ is built on IEnumerable and extension methods working with it, among other things.

Fernando
02/25/2008 07:23 PM by
Fernando

Ayende,

Maybe this is by design (callvirt vs. call). This blog post explains it: http://blogs.msdn.com/howard_dierking/archive/2007/02/09/more-c-3-0-extension-methods.aspx

Here's another one:

http://bradwilson.typepad.com/blog/2008/01/c-30-extension.html

Nathan
02/27/2008 04:52 AM by
Nathan

I'm really surprised that you had not come across this already. I've been doing this since early CTP releases. Generic extension methods are fundamental to C# 3.0 programming.

Glad you found it. Now I'm sure you'll come up with lots of interesting uses for this feature.

Comments have been closed on this topic.