score:13

Accepted answer

Not an answer to your question, but rather some musings on your design. We strongly considered adding such a feature to C# 4 but cut it because we did not have the time and resources available.

The problem with the query comprehension syntax is, as you note, that it is ugly to mix the "fluent" and "comprehension" syntaxes. You want to know how many different last names your customers have in London and you end up writing this ugly thing with parentheses:

d = (from c in customers 
     where c.City == "London" 
     select c.LastName)
    .Distinct()
    .Count();

Yuck.

We considered adding a new contextual keyword to the comprehension syntax. Let's say for the sake of argument that the keyword is "with". You could then say:

d = from c in customers 
    where c.City == "London" 
    select c.LastName
    with Distinct() 
    with Count();

and the query comprehension rewriter would rewrite that into the appropriate fluent syntax.

I really like this feature but it did not make the cut for C# 4 or 5. It would be nice to get it into a hypothetical future version of the language.

As always, Eric's musing about hypothetical features of unannounced products that might never exist are for entertainment purposes only.

score:3

On idea is that you could write your own query provider that wraps the version in System.Linq and then calls ToArray in its Select method. Then you would just have a using YourNamespace; instead of using System.Linq.

Roslyn does not allow you to extend the syntax of C#, but you can write a SyntaxRewriter that changes the semantics of a C# program as a rebuild step.

score:2

As others said, Roslyn is not what you probably think it is. It can't be used to extend C#.

All of the following code should be considered more brainstorming and less recommendation. It changes how LINQ behaves in unexpected ways and you should think really hard before using anything like it.

One way to solve this would be to modify the select clause like this:

int count = from i in Enumerable.Range(0, 10)
            where i % 2 == 0
            select new Count();

The implementation could look like this:

public  class Count
{}

public static class LinqExtensions
{
    public static int Select<T>(
        this IEnumerable<T> source, Func<T, Count> selector)
    {
        return source.Count();
    }
}

If you put anything that isn't Count in the select, it would behave as usual.

Doing something similar for arrays would take more work, since you need the select to specify both that you want an array and the selector of items you want in there, but it's doable. Or you could use two selects: one chooses the item and the other says you want an array.

Another option (similar to Kevin's suggestion) would be to have extension method like AsCount() which you could use like this:

int count = from i in Enumerable.Range(0, 10).AsCount()
            where i % 2 == 0
            select i;

You could implement it like this:

public static class LinqExtensions
{
    public static Countable<T> AsCount<T>(this IEnumerable<T> source)
    {
        return new Countable<T>(source);
    }
}

public class Countable<T>
{
    private readonly IEnumerable<T> m_source;

    public Countable(IEnumerable<T> source)
    {
        m_source = source;
    }

    public Countable<T> Where(Func<T, bool> predicate)
    {
        return new Countable<T>(m_source.Where(predicate));
    }

    public Countable<TResult> Select<TResult>(Func<T, TResult> selector)
    {
        return new Countable<TResult>(m_source.Select(selector));
    }

    // other LINQ methods

    public static implicit operator int(Countable<T> countable)
    {
        return countable.m_source.Count();
    }
}

I'm not sure I like it this way. Especially the implicit cast feels wrong, but I think there is no other way.


Related Articles