Odd query expressions

Yesterday, I was proof reading chapter 11 of the book (and chapter 12, and chapter 13 – it was a long night). Reading my own text about how query expressions work led me to wonder just how far I could push the compiler in terms of understanding completely bizarre query expressions.

Background

Query expressions in C# 3 work by translating them into “normal” C# first, then applying normal compilation. So, for instance, a query expression of:

from x in words
where x.Length > 5
select x.ToUpper()

… is translated into this:

words.Where(x => x.Length > 5)
     .Select(x => x.ToUpper())

Now, usually the source expression (words in this case) is something like a variable, or the result of a method or property call. However, the spec doesn’t say it has to be… let’s see what else we could do. We’ll keep the query expression as simple as possible – just from x in source select x.

Types

What if the source expression is a type? The query expression would then compile to TypeName.Select(x => x) which of course works if there’s a static Select method taking an appropriate delegate or expression tree. And indeed it works:

using System;

public class SourceType
{
    public static int Select(Func<string,string> ignored)
    {
        Console.WriteLine(“Select called!”);
        return 10;
    }
}

class Test
{
    static void Main()
    {
        int query = from x in SourceType
                    select x;
    }
}

That compiles and runs, printing out Select called!. query will be 10 after the method has been called.

So, we can call a static method on a type. Anything else? Well, let’s go back to the normal idea of the expression being a value, but this time we’ll change what Select(x => x) actually means. There’s no reason it has to be a method call. It looks like a method call, but then so do delegate invocations…

Delegates via properties

Let’s create a “LINQ proxy” which has a property of a suitable delegate type. Again, we’ll only provide Select but it would be possible to do other types – for instance the Where property could be typed to return another proxy.

using System;
using System.Linq.Expressions;

public class LinqProxy
{
    public Func<Expression<Func<string,string>>,int> Select { get; set; }
}

class Test
{
    static void Main()
    {
        LinqProxy proxy = new LinqProxy();
        
        proxy.Select = exp => 
        { 
            Console.WriteLine (“Select({0})”, exp);
            return 15;
        };
        
        int query = from x in proxy
                    select x;
    }
}

Again, it all compiles and runs fine, with an output of “Select(x => x)”. If you wanted, you could combine the two above approaches – the SourceType could have a static delegate property instead of a method. Also this would still work if we used a public field instead of a property.

What else is available?

Looking through the list of potential expressions, there are plenty more we can try to abuse: namespaces, method groups, anonymous functions, indexers, and events. I haven’t found any nasty ways of using these yet – if we could have methods or properties directly in namespaces (instead of in types) then we could use from x in SomeNamespace but until that time, I think sanity is reasonably safe. Of course, if you can find any further examples, please let me know!

Why is this useful?

It’s not. It’s really, really not – at least not in any way I can think of. The “proxy with delegate properties” might just have some weird, twisted use, but I’d have to see it to believe it. Of course, these are useful examples to prove what the compiler’s actually doing, and the extent to which query expressions really are just syntactic sugar (but sooo sweet) – but that doesn’t mean there’s a good use for the technique in real code.

Again, if you can find a genuine use for this, please mail me. I’d love to hear about such oddities.

4 thoughts on “Odd query expressions”

  1. That is so cool!

    I love the absurdity of delegate properties/fields. I wrote a post awhile back about type system abuses to enable generic delegate fields for implementing a hypothetical functional programming library:

    http://jacobcarpenter.wordpress.com/2008/01/02/c-abuse-of-the-day-functional-library-implemented-with-lambdas/

    I’ll have to see what I can come up with to abuse this discovery.

    Did you try creating a class (that didn’t implement IEnumerable) with a delegate property for GetEnumerator()? It’d be interesting to see if you could use instances of that class in foreach statements…

  2. I wouldn’t expect foreach to work if GetEnumerator is a property. foreach isn’t the same sort of construct as query expressions – the C# compiler “understands” foreach to a much greater extent than it understands query expressions.

    Or rather, query expressions are a sort of preprocessor phase which allows the result to be analysed by the rest of the compiler as if it were any other piece of C# code.

  3. Yeah; indeed it doesn’t. The compiler verifies that GetEnumerator is in fact a method.

    There’s even a compiler warning dedicated to that failure:

    warning CS0280: ‘Foo’ does not implement the ‘collection’ pattern. ‘Foo.GetEnumerator’ has the wrong signature.

    see here for more: http://msdn2.microsoft.com/en-us/library/a9k87wd8.aspx

    More interesting, though, is the error you get when you try to use collection initialization on an object that has an Add property/field (don’t ask).

    Inside visual studio, each element within the braces gets a blue squiggly, and the error is:
    error CS0118: ‘Foo.Add’ is a ‘property’ but is used like a ‘method’. It takes a second of looking at it to get what it’s saying.

  4. Short example (doesn’t compile):

    using System;
    using System.Collections;

    public class FakeCollection
    : IEnumerable // < - lie to leverage collection initialization
    {
    public FakeCollection() {}
    public Action Add { get; set; }
    IEnumerator IEnumerable.GetEnumerator() {
    throw new NotImplementedException();
    }
    }

    class Test
    {
    static void Main()
    {
    // error CS0118: ‘FakeCollection.Add’ is a ‘property’ but is used like a ‘method’ (x4)
    var ignored = new FakeCollection() { 1, 2, 3, 4 };
    }
    }

Comments are closed.