能否通过IEnumerable.Select()跳过一个项目?

21

我有这个函数:

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

由于可能存在具有不存在的PID的会话,因此try..catch是必需的。我想跳过它们。是否可以从Select回调中实现这一点,或者我需要添加一个新的Where条件来跳过null值?

6个回答

31

不,Select 对每个输入元素始终产生一个输出元素。没有其他选择。您可以轻松编写自己的 FilteredSelect 扩展方法,但使用 Where 子句更简单。

或者,使用 Process.GetProcesses() 获取所有进程的快照,然后将其连接到您的会话集合(或使用类似的方法)。这将避免丑陋的 catch:

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;

第二个建议听起来很有趣。但是,与音频会话相关的进程只是正在运行的进程的一小部分,所以我猜它可能会稍微不那么高效(虽然在这种情况下并不重要)? - ThiefMaster
1
@ThiefMaster:我不知道Process.GetProcesses()需要多长时间,但至少你只需要调用它一次,而不是每个会话都调用一次。 - Jon Skeet
1
@ThiefMaster 我非常确定 GetProcessesGetProcessByID 都在背后调用了 EnumProcesses - Rotem

6

在John Skeet的文章基础上,这个扩展方法为我节省了无数行代码。名字也非常贴切:SelectWhere。以下是一个你可以使用的扩展方法代码清单。

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

使用方法:

entity.SelectWhere(e => e.FirstName, e => e.Age>25);

5

Linq中的Select相当于Map,而Aggregate相当于Reduce。 Map / Select是1:1输入到输出。 如果没有1:1关系,您可以使用Reduce / Aggregate

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

4

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 */);

2

1

这里有一种更简单的使用LINQ的扩展方法:

public static IEnumerable<TResult> FilteredSelect<TSource, TResult>(this IEnumerable<TSource> source, Func<TSource, bool> predicate, Func<TSource, TResult> selector)
{
    return source
        .Where(item => predicate(item))
        .Select(item => selector(item));
}

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接