Доступны ли опции для "поддельного" ключевого слова синтаксиса linq в C#?

Хотя есть несколько случаев, когда я что-то напишу, используя цепочки методов (особенно, если это всего лишь один или два метода, например, foo.Where(..).ToArray()), во многих случаях я предпочитаю синтаксис понимания запросов LINQ. вместо этого ("выражения запроса" в спецификации), что-то вроде:

var query =
    from filePath in Directory.GetFiles(directoryPath)
    let fileName = Path.GetFileName(filePath)
    let baseFileName = fileName.Split(' ', '_').First()
    group filePath by baseFileName into fileGroup
    select new
    {
        BaseFileName = fileGroup.Key,
        Count = fileGroup.Count(),
    };

В некоторой довольно значительной части из них мне нужно взять полученный IEnumerable и нетерпеливо загрузить его в структуру данных (массив, список, что угодно). Это обычно означает либо:

  1. добавление другой локальной переменной, такой как var queryResult = query.ToArray(); или же

  2. оборачивая запрос паренами и помечая тегами ToArray (или ToList или что-то еще).

var query = (
    from filePath in Directory.GetFiles(directoryPath)
    let fileName = Path.GetFileName(filePath)
    let baseFileName = fileName.Split(' ', '_').First()
    group filePath by baseFileName into fileGroup
    select new
    {
        BaseFileName = fileGroup.Key,
        Count = fileGroup.Count(),
    }
).ToArray();

Я пытаюсь выяснить, какие варианты другие либо 1) уже используют, либо 2) могут посчитать целесообразным добавление некоторых дополнительных "контекстных ключевых слов" - просто вещи, которые будут преобразовываться в методы расширения так же, как и существующие, как если бы ключевые слова LINQ были "изначально" расширяемыми:)

Я понимаю, что, скорее всего, это будет означать либо какую-то предварительную обработку (не уверен, что там есть в этой области для C#), либо изменение используемого компилятора на что-то вроде Nemerle (я думаю, что это будет вариант, но не совсем уверен?). Я пока не знаю достаточно о том, что Roslyn делает / будет поддерживать, поэтому, если кто-то знает, может ли он позволить кому-либо "расширять" C#, пожалуйста, присоединяйтесь!

Те, которые я, вероятно, использовал бы больше всего (хотя я уверен, что есть много других, но просто чтобы донести идею / на что я надеюсь):

ascount - преобразуется в Count()

int zFileCount =
    from filePath in Directory.GetFiles(directoryPath)
    where filePath.StartsWith("z")
    select filePath ascount;

Это "преобразовало бы" (не важно, какой путь, если конечный результат) в:

int zFileCount = (
    from filePath in Directory.GetFiles(directoryPath)
    where filePath.StartsWith("z")
    select filePath
).Count();

Так же:

  • asarray - преобразуется в ToArray ()
  • aslist - преобразуется в ToList ()

(очевидно, вы могли бы продолжать использовать First(), Single(), Any() и т. д., но пытаться контролировать объем вопроса:)

Меня интересуют только методы расширения, которым не нужно передавать параметры. Я не ищу попытки сделать что-то подобное с (например) ToDictionary или ToLookup.:)

Итак, в итоге:

  • хотите добавить 'ascount', 'aslist' и 'asarray' в выражения запроса linq
  • не знаю, было ли это уже решено
  • не знаю, является ли Nemerle хорошим выбором для этого
  • не знаю, поддержит ли история Roslyn этот вид использования

3 ответа

Решение

Не ответ на ваш вопрос, а некоторые размышления о вашем дизайне. Мы решительно решили добавить такую ​​возможность в C# 4, но сократили ее, потому что у нас не было времени и ресурсов.

Проблема с синтаксисом понимания запросов, как вы заметили, состоит в том, что смешивать синтаксис "беглый" и "понимание" некрасиво. Вы хотите знать, сколько разных фамилий у ваших клиентов в Лондоне, и в итоге вы пишете эту уродливую вещь в скобках:

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

Тьфу.

Мы рассмотрели возможность добавления нового контекстного ключевого слова в синтаксис понимания. Скажем ради аргумента, что ключевое слово "с". Вы могли бы тогда сказать:

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

и переписчик для понимания запросов переписал бы это в соответствующий свободный синтаксис.

Мне очень нравится эта функция, но она не подходит для C# 4 или 5. Было бы неплохо включить ее в гипотетическую будущую версию языка.

Как всегда, Эрик размышляет о гипотетических особенностях необъявленных продуктов, которые могут никогда не существовать, только для развлекательных целей.

По идее, вы могли бы написать свой собственный поставщик запросов, который переносит версию в System.Linq а потом звонит ToArray в его Select метод. Тогда вы бы просто using YourNamespace; вместо using System.Linq,

Roslyn не позволяет вам расширять синтаксис C#, но вы можете написать SyntaxRewriter это меняет семантику программы на C# как шаг перестройки.

Как говорили другие, Рослин - это не то, о чем вы, вероятно, думаете. Его нельзя использовать для расширения C#.

Весь следующий код следует считать более мозговым штурмом и меньшим количеством рекомендаций. Это меняет поведение LINQ неожиданным образом, и вы должны очень серьезно подумать, прежде чем использовать что-либо подобное.

Одним из способов решения этой проблемы является изменение select пункт, как это:

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

Реализация может выглядеть так:

public  class Count
{}

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

Если вы положите что-нибудь, что не Count в select, он будет вести себя как обычно.

Создание чего-то похожего для массивов потребует больше работы, так как вам нужно select указать, какой массив вам нужен, и какой селектор элементов вы хотите там, но это выполнимо. Или вы могли бы использовать два selects: один выбирает элемент, а другой говорит, что вы хотите массив.

Другой вариант (аналогичный предложению Кевина) - использовать такой метод расширения, как AsCount() который вы могли бы использовать так:

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

Вы можете реализовать это так:

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();
    }
}

Я не уверен, что мне так нравится. Особенно неявный актерский состав кажется неправильным, но я думаю, что другого пути нет.

Другие вопросы по тегам