Может ли IEnumerable.Select() пропускать элемент?

У меня есть эта функция:

public IEnumerable<string> EnumPrograms() {
    return dev.AudioSessionManager2.Sessions.AsEnumerable()
        .Where(s => s.GetProcessID != 0)
        .Select(s => {
            try {
                return Process.GetProcessById((int)s.GetProcessID).ProcessName;
            }
            catch (ArgumentException) {
                return null;
            }
        });
}

Файл try..catch необходим, поскольку могут быть сеансы с PID, который больше не существует. Я бы хотел пропустить их. Есть ли способ сделать это из обратного вызова Select или мне нужно добавить новое условие Where, которое пропускает значения null?

Ответ 1

Нет, Select всегда дает один выходной элемент для каждого элемента ввода. Нет альтернативы этому. Вы можете легко написать собственный метод расширения FilteredSelect - но проще просто использовать предложение Where.

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

var sessionProcessIds = new HashSet<int>(dev.AudioSessionManager2.Sessions
                                            .AsEnumerable()
                                            .Select(x => x.GetProcessId)
                                            .Where(pid => pid != 0));
var processes = Process.GetProcesses();
var sessionProcessNames = processes.Where(p => sessionProcessIds.Contains(p.Id))
                                   .Select(p => p.ProcessName);

Или:

var names = from session in dev.AudioSessionManager2.Sessions.AsEnumerable()
            let pid = session.GetProcessId
            where pid != 0
            join process in Process.GetProcesses() on pid equals process.Id
            select process.ProcessName;

Ответ 2

Select не может это сделать сам по себе, вы можете создать собственный метод расширения для него, как упомянуто @Jon Skeet.

public static IEnumerable<TResult> FilteredSelect<TSource, TResult>(
    this IEnumerable<TSource> source
    , Func<TSource, bool> predicate
    , Func<TSource, TResult> selector)
{
    foreach (var item in source)
    {
        if (predicate(item))
        {
            yield return selector(item);
        }
    }
}

И используйте как

elements.FilteredSelect(/* where condition */, /* select values */);

Ответ 3

Select в Linq эквивалентен Map, а Aggregate эквивалентен Reduce. Карта/Выбор - вход 1:1 для вывода. Вы хотите использовать Reduce/Aggregate, если нет отношения 1:1.

public IEnumerable<string> EnumPrograms() {
    return dev.AudioSessionManager2.Sessions.AsEnumerable()
        .Where(s => s.GetProcessID != 0)
        .Aggregate(new List<string>(), (acc, s) => {
            try {
                var proc = Process.GetProcessById((int)s.GetProcessID).ProcessName;
                acc.Add(proc);
            } catch (ArgumentException) { }
            return acc;
    });
}