在LINQ中过滤时构建外部列表

时间:2010-03-29 02:53:19

标签: linq

我有一组输入字符串,其中包含域\帐户形式的电子邮件地址或帐户名称。我想构建一个只包含电子邮件地址的字符串列表。如果输入数组中的元素是domain \ account形式,我将在字典中执行查找。如果在字典中找到密钥,则该值是电子邮件地址。如果未找到,则不会将其添加到结果列表中。下面的代码将使上述说明清楚:

private bool where(string input, Dictionary<string, string> dict)
{
    if (input.Contains("@"))
    {                
        return true;
    }
    else
    {
       try
       {
           string value = dict[input];             
           return true;
       }
       catch (KeyNotFoundException)
       {
           return false;
       }
    }
}

private string select(string input, Dictionary<string, string> dict)
{
    if (input.Contains("@"))
    {                
        return input;
    }
    else
    {
        try
        {
            string value = dict[input];                    
            return value;
        }
        catch (KeyNotFoundException)
        {
            return null;
        }
    }
}
public void run()
{
    Dictionary<string, string> dict = new Dictionary<string, string>()
    {
        { "gmail\\nameless", "nameless@gmail.com"}
    };    

    string[] s = { "anonymous@gmail.com", "gmail\\nameless", "gmail\\unknown" };
    var q = s.Where(p => where(p, dict)).Select(p => select(p, dict));
    List<string> resultList = q.ToList<string>();
}

虽然以上代码有效(希望我这里没有任何拼写错误),但我不喜欢上述两个问题:

  1. 其中()和select()的代码似乎是冗余/重复。
  2. 需要2次传球。第二遍从查询表达式转换为List。
  3. 所以我想直接在where()方法中添加List resultList。好像我应该能够这样做。这是代码:

    private bool where(string input, Dictionary<string, string> dict, List<string> resultList)
    {
        if (input.Contains("@"))
        {                
            resultList.Add(input);  //note the difference from above
            return true;
        }
        else
        {
           try
           {
               string value = dict[input];
               resultList.Add(value); //note the difference from above             
               return true;
           }
           catch (KeyNotFoundException)
           {
               return false;
           }
        }
    }
    

    我的LINQ表达式可以很好地用于单个语句:

    List<string> resultList = new List<string>();
    s.Where(p => where(p, dict, resultList));
    

    或者

    var q = s.Where(p => where(p, dict, resultList)); //do nothing with q afterward
    

    这似乎是完美合法的C#LINQ。结果:有时它可以工作,有时则不然。那么为什么我的代码不能可靠地工作呢?我怎么能这样做呢?

4 个答案:

答案 0 :(得分:2)

如果您反转where和select,您可以先将未知域帐户转换为null,然后将其过滤掉。

private string select(string input, Dictionary<string, string> dict)
{
    if (input.Contains("@"))
    {                
        return input;
    }
    else
    {
        if (dict.ContainsKey(input))
            return dict[input];
    }
    return null;
}

var resultList = s
    .Select(p => select(p, dict))
    .Where(p => p != null)
    .ToList()

这会处理您的重复代码。

  

需要2次传球。第二遍将查询表达式转换为List。

实际上这只是一次传递,因为LINQ是惰性评估的。这就是为什么你的最后陈述有时只能起作用。仅应用过滤器,并在评估LINQ查询时生成列表。否则,Where语句永远不会运行。

答案 1 :(得分:1)

听起来你想要的是一个迭代器。通过创建自己的迭代器,您可以过滤列表并同时生成输出。

public static IEnumerable EmailAddresses(IEnumerable<string> inputList,
    Dictionary<string, string> dict)
{
    foreach (string input in inputList)
    {
        string dictValue;
        if (input.Contains("@"))
            yield return input;
        else if (TryGetValue(input, out dictValue)
            yield return dictValue;
        // else do nothing
    }
}

List<string> resultList = EmailAddresses(s, dict).ToList();

答案 2 :(得分:0)

您通常不希望像列表一样对不相关的对象产生副作用。这使得理解,调试和重构变得困难。我不担心优化查询,直到你知道它的表现不佳。

那么,你的原始表达有什么问题?您不需要select和where。您只需要Where()调用。这将返回一个电子邮件地址列表,您可以将其粘贴到HashSet中。 HashSet将提供您想要的唯一性。这将增加执行时间,因此如果您不需要它,请不要使用它。

你应该只需要:

var s = new[] {"me@me.com", "me_not_at_me.com", "not_me"};
var emailAddrs = s.Where( a => a.Contains("@")); // This is a bad email address validator; find a better one.
var uniqueAddrs = new HashSet<string>(emailAddrs);

(注意,我没有处理HashSet,所以构造函数可能不会使用Enumerable。这对读者来说是一个练习。)

答案 3 :(得分:0)

这是使用LINQ进行处理的一种方法。它根据值是否为电子邮件地址对值进行分组,从而产生2组字符串。如果一个组是电子邮件地址组,我们会直接从中选择,否则我们会查找电子邮件并从中选择:

public static IEnumerable<string> SelectEmails(
    this IEnumerable<string> values,
    IDictionary<string, string> accountEmails)
{
    return
        from value in values
        group value by value.Contains("@") into valueGroup
        from email in (valueGroup.Key ? valueGroup : GetEmails(valueGroup, accountEmails))
        select email;
}

private static IEnumerable<string> GetEmails(
    IEnumerable<string> accounts,
    IDictionary<string, string> accountEmails)
{
    return
        from account in accounts
        where accountEmails.ContainsKey(account)
        select accountEmails[account];
}

您可以这样使用它:

var values = new string[] { ... };
var accountEmails = new Dictionary<string, string> { ... };

var emails = values.SelectEmails(accountEmails).ToList();

当然,实现这种扩展方法最直接的方法是@gabe的方法。