Доступны ли опции для "поддельного" ключевого слова синтаксиса 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 и нетерпеливо загрузить его в структуру данных (массив, список, что угодно). Это обычно означает либо:
добавление другой локальной переменной, такой как var queryResult = query.ToArray(); или же
оборачивая запрос паренами и помечая тегами 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
указать, какой массив вам нужен, и какой селектор элементов вы хотите там, но это выполнимо. Или вы могли бы использовать два select
s: один выбирает элемент, а другой говорит, что вы хотите массив.
Другой вариант (аналогичный предложению Кевина) - использовать такой метод расширения, как 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();
}
}
Я не уверен, что мне так нравится. Особенно неявный актерский состав кажется неправильным, но я думаю, что другого пути нет.