Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Cannot convert source type to target type List<KeyValuePair> Linq

Tags:

c#

linq

I have a method that is a List<KeyValuePair<int,string>> that returns a list; however, I'm getting an error:

Cannot convert source type System.Collections.Generic.List<{Key:int, Value:string}> to target type System.Collections.Generic.KeyValuePair<'int,string'>>.

I am trying to make a Linq select statement into a new list but I don't understand if the initialized list is the problem or if its how I'm getting the values in the Linq statement.

Here is the method:

public static List<KeyValuePair<int,string>> GetStatus(int status)
{
   List<KeyValuePair<int,string>> st = SelectList.GetStatuses();
   List<KeyValuePair<int,string>> tp;

   switch(status)
   {
      case (int)Status.Completed:
          tp = st.Where(s => s.Key == (int)Status.Completed ||
                             s.Key == (int)Status.NotValid)
                 .Select(s => new { s.Key, s.Value }).ToList();
      case (int)Status.Open:
          tp = st.Where(s => s.Key == (int)Status.Open ||
                             s.Key == (int)Status.Reviewed)
                 .Select(s => new { s.Key, s.Value }).ToList();
      default:
          break;
   }
   return tp;
}

Here's the method that fills the list:

public static List<KeyValuePair<int, string>> GetStatuses()
    {
        using (var con = new SqlConnection())
        {
            var sql = @"SELECT ID [Key], Text [Value] from Statuses";

            return (con.Query<KeyValuePair<int, string>>(sql.ToString(), commandType: commandType:Text) ?? Enumerable.Empty<KeyValuePair<int, string>>()).ToList();
        }
    }
like image 665
Elaine K Avatar asked Dec 19 '22 19:12

Elaine K


2 Answers

When you write new { s.Key, s.Value } you are instantiating a new anonymous type with properties Key and Value. Instead, you probably meant to use the contructor of KeyValuePair by writing new KeyValuePair(s.Key, s.Value).

Note, too, that your Where clause is already filtering a list of KeyValuePairs, and so no projection is even necessary. In other words, you can drop the entire Select statement in this case.

So, you could use the KeyValuePair constructor and write:

tp = st.Where(s => s.Key == (int)Status.Open || s.Key == (int)Status.Reviewed)
       .Select(kvp => new KeyValuePair(kvp.Key, kvp.Value)).ToList();

Or, even better, simply drop the Select statement:

tp = st.Where(s => s.Key == (int)Status.Open || s.Key == (int)Status.Reviewed).ToList();

A little bit more on anonymous types: Anonymous types are a C# features that allow you to make quick ad-hoc classes with custom properties. They are especially useful with Linq queries. When your program compiles, a new anonymous type is emitted with 2 properties named Key and Value. You can thing of this a new class, without a name, that has exactly 2 properties (there are some other differences between full-fledged classes and anonymous types, but this is a convenient way to think about it). Your method is expected to return a list of KeyValuePairs, but you have provided a list of objects of this new type. Read more about them here.

As a general minor note, you do not need to declare the tp variable at all. Instead you can return within your switch statement:

like image 180
Ben Reich Avatar answered Jan 05 '23 00:01

Ben Reich


As others have already pointed out, you are creating a new anonymous type in your Select, not a KeyValuePair. However, you are already working against a KeyValuePair, so you do not even need the Select

tp = st.Where(...where...).ToList();

There is really no point to re-instantiating the objects in the Select (unless you want a new reference...but KVP is immutable in this case). The Where.ToList is more expressive simply because it is less to grok, at least IMHO

like image 38
Justin Pihony Avatar answered Jan 04 '23 23:01

Justin Pihony