用Linq查询替换for-switch循环

时间:2011-09-30 08:07:34

标签: c# linq

我有一个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<User> ParseUsers( Message message )
{
    List<User> users = new List<User>( );
    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)。

4 个答案:

答案 0 :(得分:5)

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

private List<User> 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<IEnumerable<T>> GroupAdjacent<T>(
    this IEnumerable<T> source, Func<IEnumerable<T>, T, bool> adjacent)
{
    var g = new List<T>();
    foreach (var x in source)
    {
        if (g.Count != 0 && !adjacent(g, x))
        {
            yield return g;
            g = new List<T>();
        }
        g.Add(x);
    }
    yield return g;
}

答案 1 :(得分:1)

如何将邮件拆分为List<List<KeyValuePait<int, string>>>,其中每个List<KeyValuePair<int, string>>代表一个用户。然后你可以做类似的事情:

// SplitToUserLists would need a sensible implementation.
List<List<KeyValuePair<int,string>>> splitMessage = message.SplitToUserLists();
IEnumerable<User> users = splitMessage.Select(ConstructUser);

使用

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

答案 2 :(得分:1)

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

答案 3 :(得分:1)

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

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

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

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

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