我有一组输入字符串,其中包含域\帐户形式的电子邮件地址或帐户名称。我想构建一个只包含电子邮件地址的字符串列表。如果输入数组中的元素是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>();
}
虽然以上代码有效(希望我这里没有任何拼写错误),但我不喜欢上述两个问题:
所以我想直接在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。结果:有时它可以工作,有时则不然。那么为什么我的代码不能可靠地工作呢?我怎么能这样做呢?
答案 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的方法。