简体   繁体   中英

System.Linq.Enumerable+d__3a`1[System.String] error in LINQ foreach loop

I have a list that I am trying to populate from another list in which I want to combine some data and eliminate some.

In the original list the data looks like this:

 Id     Text      Description

  1      Apple     Red Delicious
  1      Apple     Green
  1      Apple     Granny 
  2      Tomato    Roma
  2      Tomato    Cherry   

I want to condense this information in the second list to look like this:

  Id    Text     Description
  1     Apple    Red Delicious, Green, Granny
  2     Tomato   Roma, Cherry 

My class object is declared like this:

 [Serializable]
 public class Food
 {
     public int Id { get;set; }
     public string Text { get;set; }
     public string Description { get;set; } 
 }

So I want to loop through the old list and this is how I'm trying to do it in the code:

var ids = oldList.Select(i => i.Id).Distinct(); //get distinct list of ids (e.g. 1,2)
List<Food> newList = ids.Select(i => new Food(){
   Id = i,
   Text = oldList.Where(o => o.Id == i).Select(o => o.Text).Take(1).ToString(),
   Description = string.Join(",", oldList.Where(o => o.Id == i).Select(o => o.Description).ToString())
}).ToList();

Right now I'm getting the System.Linq.Enumerable+d__3a`1[System.String] error because of .Take(), but if I change it just .ToString(), I get a slightly different error but from the same source System.linq.Enumerable, if I do FirstOrDefault() same thing, .Distinct() same thing.

I think I understand that the problem is that for Text and Description its returning IEnumerable for Text and Description so I'm not getting the actual values that I want and I'm not sure I understand how to correctly convert it .ToList(). What is the correct way to access these values?

You're calling ToString() on the result of Take and Select - and that's not going to do anything nice. It's not clear why you're calling ToString explicitly in Description , and for Text you really want FirstOrDefault or First instead, as you just want the first result:

List<Food> newList = ids.Select(i => new Food {
   Id = i,
   Text = oldList.Where(o => o.Id == i).Select(o => o.Text).FirstOrDefault(),
   Description = string.Join(",", oldList.Where(o => o.Id == i)
                                         .Select(o => o.Description))
}).ToList();

Basically, calling ToString() on a sequence ( IEnumerable<T> ) is almost never appropriate.

This is typical job for GroupBy operator:

var newList = oldList.GroupBy(x => x.Id, 
                        (key, values) => 
                           new Food() {
                             Id = key,
                             Text = values.First().Text,
                             Description = string.Join(", ", 
                                              values.Select(v => v.Description))
                            })
                     .SelectMany(x => x)
                     .ToList();

I think that you are perhaps overcomplicating your approach.

This is a very roughly hacked-together sample (you can run it in LinqPad if you like), but it uses .GroupBy to solve the same problem...

var l = new List<Tuple<int, string, string>>();

l.Add(Tuple.Create(1, "Apple", "Red Delicious"));
l.Add(Tuple.Create(1, "Apple", "Green"));
l.Add(Tuple.Create(1, "Apple", "Granny"));
l.Add(Tuple.Create(2, "Tomato", "Roma"));
l.Add(Tuple.Create(2, "Tomato", "Cherry"));

var res = l.GroupBy(t => t.Item2).Select(g => new { Id = g.First().Item1, Text = g.Key, Description = string.Join(", ", g.Select(i => i.Item3)) });
res.Dump(); // Linqpad output

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM