C# 用Linq查询替换开关循环

C# 用Linq查询替换开关循环,c#,linq,C#,Linq,我有一个消息对象,它包装了一种我无法控制的消息格式。格式是键/值对的简单列表。我想从给定的消息中提取用户列表。例如,给定以下消息 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_us

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

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;
}
每个用户的数据都在连续的字段中,但字段的数量取决于用户的已知信息量。下面的方法符合我的要求。它使用可能的键类型枚举和一种方法来查找包含用户数据的第一个字段的索引

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;
}
私有列表解析用户(消息)
{
列表用户=新列表();
User=null;String val=String.Empty;
for(Int32 i=message.IndexOfFirst(key.Name);i
我想知道是否可以用Linq查询替换该方法。我很难告诉Linq选择一个新用户并用所有匹配的数据填充其字段,直到找到下一个用户条目的开始


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

如何将消息拆分为一个
列表
,其中每个
列表
代表一个用户。然后,您可以执行以下操作:

// SplitToUserLists would need a sensible implementation.
List<List<KeyValuePair<int,string>>> splitMessage = message.SplitToUserLists();
IEnumerable<User> users = splitMessage.Select(ConstructUser);
//SplitToUserList需要一个合理的实现。
List splitMessage=message.splittoUserList();
IEnumerable users=splitMessage.Select(ConstructUser);

私有用户ConstructUser(列表用户列表)
{
返回userList.Aggregate(新用户(),(用户,keyValuePair)=>User[keyValuePair.Key]=keyValuePair.Val);
}

否,原因是,一般来说,大多数LINQ函数与SQL查询处理无序数据的方式相同,即它们不对传入数据的顺序进行假设。这给了它们并行化的灵活性,等等。您的数据具有内在的顺序,因此不适合查询模型。

我不认为将代码更改为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();
}
私有列表解析用户(消息)
{
返回可枚举
.Range(0,message.Count)
.选择(i=>message[i])
.SkipWhile(x=>x.Key!=Keys.Name)
.groupnexting((g,x)=>x.Key!=Key.Name)
.Select(g=>g.ToDictionary(x=>x.Key,x=>x.Val))
.Select(d=>newuser(d[Keys.Name])
{
Phone=d.ContainsKey(Keys.Phone)?d[Keys.Phone]:空,
Fax=d.ContainsKey(key.Fax)?d[key.Fax]:空,
地址=d.ContainsKey(key.Address)?d[key.Address]:空,
})
.ToList();
}
使用

静态IEnumerable组(
此IEnumerable源,Func(相邻)
{
var g=新列表();
foreach(源中的变量x)
{
如果(g.Count!=0&&!相邻(g,x))
{
收益率g;
g=新列表();
}
g、 加(x);
}
收益率g;
}

我不认为这对性能有任何好处,但在我看来,这大大提高了可读性

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

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

你在用重竖琴吗?它非常擅长将循环重构为LINQ表达式。将其转换为LINQ查询有什么好处?你的代码在我看来是不错的。@marian。。谢谢你的建议,我还没有用过Resharper,但我会用它look@dtb .. 谢谢代码的工作原理是肯定的,但每次我通过它的文件时,我都有一种恼人的感觉,它可能会被替换为几行Linq+1:回答OP的问题,并且非常有说服力地告诉他不要再使用他优秀的代码了。@dtb。。是的,编辑过的版本很有用。。哈哈。。我还不完全清楚我到底是怎么理解的,但再次感谢。。很高兴看到总是有办法做某事。。通过阅读你的code@tim.. 是的,我在想,当我写循环的时候,但每次我跳过它,我都会有一种烦琐的感觉,觉得这是可能的..@joey。。嗨,乔伊。。谢谢你的来信。。一直在摆弄dtb的代码。。现在可以享受了(谢谢dtb)。。我必须重新实现我的用户类,才能按原样使用ConstructUser,并且我会以这种方式多次阅读消息。。但是看到不同的方法很有趣!再次谢谢你,没问题。我认为最好的解决方案仍然是你开始的那一个。尽管可能尝试将开关重构为User或某个UserBuilder对象。+1。。是的,这是个好建议。。按照u的建议/通过GroupInnextant或类似的方式向它传递一个可枚举的字段拆分,让它自己创建,用户格式不太可能改变(啊哼!)。。。因此,我认为我应该使用User而不是UserFactory/UserBuilder,但即使如此,将创建代码放在用户中也比将其放在另一个位置的交换机中要好。谢谢乔伊,你好,费杜蒙。。谢谢你的发帖。。我仍然支持dtb的答案,因为它是完整的,即接受消息输入并返回用户列表。此外,当存在非用户数据时,您的解决方案似乎会中断。ie原始示例似乎只在删除200/300/405字段的情况下工作@
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;
}
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