2010-03-29 38 views
1

我有一个输入字符串数组,其中包含域\帐户形式的电子邮件地址或帐户名。我想建立一个只包含电子邮件地址的字符串列表。如果输入数组中的元素的形式为domain \ account,我将在字典中执行查找。如果在字典中找到密钥,那么该值就是电子邮件地址。如果未找到,则不会将其添加到结果列表中。下面将代码使上面的描述中明确:在LINQ中过滤时创建外部列表

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", "[email protected]"} 
    };  

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

虽然上面的代码工作(希望我没有在这里任何错字),有2个问题,我不跟上面一样:

  1. where()和select()中的代码似乎是冗余/重复的。
  2. 需要2次通过。第二遍从查询表达式转换为List。

所以我想直接在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表达式可以在1个单个语句是很好的:

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。结果是:有时它可以工作,有时它不会。那么为什么我的代码不能可靠地工作,我该如何做到这一点呢?

回答

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语句永远不会运行。

+0

满意你的答案,解决了我的两个问题。当我看到它只在部分时间内有效时,我以为我疯了。 LINQ懒惰评估解释它。 – 2010-03-29 06:02:13

0

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

那么,你原来的表情有什么问题?你不需要select和where。你只需要Where()调用。这将返回一个电子邮件地址列表,您可以将其保存到HashSet中。 HashSet将提供您似乎期望的唯一性。这会增加执行时间,所以如果你不需要它,不要使用它。

你只应该真正需要的东西,如:(。注意,我没有处理的HashSet,这样的构造可能不会采取可枚举这将是读者的练习)

var s = new[] {"[email protected]", "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); 

+0

同意电子邮件地址验证程序,很好,但这不是问题的本质,以上只是一个例子。 – 2010-03-29 04:18:46

+0

关于副作用的好处。 – 2010-03-29 14:40:05

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(); 
+0

+1迭代器岩石。 – 2010-03-29 04:50:41

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