用Linq查询替换for-switch循环

我有一个Message对象,它包含了一个我无法控制的消息格式。 格式是键/值对的简单列表。 我想从给定的消息中提取用户列表。 例如,给出以下消息……

1. 200->.... 2. 300->.... 3. .... 4. 405->.... 5. 001->first_user_name 6. 002->first_user_phone 7. 003->first_user_fax 8. 001->second_user_name 9. 001->third_user_name 10. 002->third_user_phone 11. 003->third_user_fax 12. 004->third_user_address 13. ..... 14. 001->last_user_name 15. 003->last_user_fax 

我想用提供的属性集提取四个用户。 初始键200/300 …. 405表示我不需要的字段,可以跳过以获取用户数据。

每个用户数据都在连续的字段中,但字段的数量取决于有关用户的信息量。 以下方法可以满足我的需求。 它使用可能的键类型的枚举和方法来查找具有用户数据的第一个字段的索引。

 private List ParseUsers( Message message ) { List users = new List( ); User user = null; String val = String.Empty; for( Int32 i = message.IndexOfFirst( Keys.Name ); i < message.Count; i++ ) { val = message[ i ].Val; switch( message[ i ].Key ) { case Keys.Name: user = new User( val ); users.Add( user ); break; case Keys.Phone: user.Phone = val; break; case Keys.Fax: user.Fax = val; break; case Keys.Address: user.Address = val; break; default: break; } } return users; } 

我想知道是否有可能用Linq查询替换该方法。 我无法告诉Linq选择新用户并使用所有匹配的数据填充其字段,直到找到下一个用户条目的开头。

注意:相对密钥编号在实际消息格式中是随机的(不是1,2,3,4)。

我没有看到将代码更改为LINQ查询的好处,但它绝对可能:

 private List ParseUsers(Message message) { return Enumerable .Range(0, message.Count) .Select(i => message[i]) .SkipWhile(x => x.Key != Keys.Name) .GroupAdjacent((g, x) => x.Key != Keys.Name) .Select(g => g.ToDictionary(x => x.Key, x => x.Val)) .Select(d => new User(d[Keys.Name]) { Phone = d.ContainsKey(Keys.Phone) ? d[Keys.Phone] : null, Fax = d.ContainsKey(Keys.Fax) ? d[Keys.Fax] : null, Address = d.ContainsKey(Keys.Address) ? d[Keys.Address] : null, }) .ToList(); } 

运用

 static IEnumerable> GroupAdjacent( this IEnumerable source, Func, T, bool> adjacent) { var g = new List(); foreach (var x in source) { if (g.Count != 0 && !adjacent(g, x)) { yield return g; g = new List(); } g.Add(x); } yield return g; } 

不,原因在于,一般来说,大多数LINQ函数与SQL查询一样,处理无序数据,即它们不会对传入数据的顺序做出假设。 这使它们具有并行化的灵活性等。您的数据具有内在的顺序,因此不适合查询模型。

如何将消息拆分为List>>其中每个List>表示单个用户。 然后你可以这样做:

 // SplitToUserLists would need a sensible implementation. List>> splitMessage = message.SplitToUserLists(); IEnumerable users = splitMessage.Select(ConstructUser); 

 private User ConstructUser(List> userList) { return userList.Aggregate(new User(), (user, keyValuePair) => user[keyValuePair.Key] = keyValuePair.Val); } 

我认为没有任何性能优势,但在我看来它增加了可读性。

可能的解决方案可能如下所示:

 var data = File.ReadAllLines("data.txt") .Select(line => line.Split(new[] {"->"}, StringSplitOptions.RemoveEmptyEntries)) .GroupByOrder(ele => ele[0]); 

真正的魔力发生在GroupByOrder背后,这是一种扩展方法。

 public static IEnumerable> GroupByOrder(this IEnumerable source, Func keySelector) where K : IComparable { var prevKey = keySelector(source.First()); var captured = new List(); foreach (var curr in source) { if (keySelector(curr).CompareTo(prevKey) <= 0) { yield return captured; captured = new List(); } captured.Add(curr); } yield return captured; } 

(免责声明:Tomas Petricek的想法被盗)

您的示例数据生成以下组,现在只需要将其解析为您的User对象。

 User: first_user_name first_user_phone first_user_fax User: second_user_name User: third_user_name third_user_phone third_user_fax third_user_address User: last_user_name last_user_fax