代码之家  ›  专栏  ›  技术社区  ›  Kevin Le - Khnle

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

  •  1
  • Kevin Le - Khnle  · 技术社区  · 16 年前

    我有一个输入字符串数组,其中包含域\帐户形式的电子邮件地址或帐户名。我想建立一个字符串列表,其中只包含电子邮件地址。如果输入数组中的元素的格式为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. where()和select()中的代码似乎是多余的/重复的。
    2. 需要两次传球。第二步从查询表达式转换为列表。

    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
    

    看起来是完美合法的。结果是:有时有效,有时无效。那么,为什么我的代码不能可靠地工作,我如何才能让它这样做呢?

    4 回复  |  直到 16 年前
        1
  •  2
  •   Cameron MacFarland    16 年前

    如果您反转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()
    

    这会处理重复的代码。

    实际上这只是一个过程,因为LINQ是惰性的。这就是为什么你最后的陈述有时只能起作用。只有在对LINQ查询求值时,才会应用筛选器并生成您的列表。否则,Where语句永远不会运行。

        2
  •  1
  •   Gabe Timothy Khouri    16 年前

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

    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();
    
        3
  •  0
  •   CWF    16 年前

    一般来说,你不想对一个不相关的对象(比如你的列表)产生副作用。这使得理解、调试和重构变得困难。在您知道查询的性能不好之前,我不会担心优化查询。

    你应该只需要这样的东西:

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

        4
  •  0
  •   Bryan Watts    16 年前

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

    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的方法。