Error converting IEnumerable to List

Solarion

Honorary Master
Joined
Nov 14, 2012
Messages
28,048
Reaction score
17,803
Pretty much this is it. Something simple has gone wrong but causing big headaches!

DB Layer (Dapper)

Screenshot-1.jpg


Creating List
Screenshot-2.jpg


Initializing, and this is where it goes pear shaped

Screenshot-3.jpg


.ToList() just makes the forest even thicker.

Screenshot-4.jpg


I've trawled over several boards and the answers they provided do not work, however it may have something to do with the fact that my DB Layer is returning a Task. Any ideas please guys?

Probably going to kick myself at where I've gone wrong :popcorn:
 
Taking a guess but:
Code:
students = (await _studentRepository.getStudents()).ToList();
 
Holy Hammocks. But why?? :oops:

The short version:

Code:
students = await _repo.GetStudents();

I believe GetStudents() points to the next item in an Iterable object. Only once the await is complete is it an IEnumerable, so putting it in parentheses forces it to complete and ToList() is called on that result.

Similar to
Code:
Bool result = 1 == 1;
string value = result.ToString();
being the same as
Code:
string value = (1 == 1).ToString();

Maybe it will help reading up on the yield statement in C#, code an example and debug through it to see how it works.
 
I will do. I sort of get it but will need to dig a bit deeper. Thanks a lot Hamster.
 
Holy Hammocks. But why?? :oops:
Because C# doesn't support Higher Kinded Types (HKTs) also called Higher Kind Polymorphism.

Linq's IEnumerable is a monad, but because of a lack of HKTs it is not possible to maintain the same result type as the input type.

Linq converts your input type to IEnumerable; the monad type, on which you perform all your Linq operations, and then at the end you have to use a toList, toArray, toDictionary, etc. method call to get back to your input type.

The same language constraint is applicable for C#'s other monad types like Task i.e. affecting for example all your async, await code.

Note:
Linq is also designed to lazily compute its result to avoid unnecessary compute cost; meaning your Linq code does not actually run until you use one of thetoList, toArray, toDictionary, etc. methods.

FYI... In Haskell e.g. all computations are lazy, and the language supports HKTs.
 
Last edited:
Because C# doesn't support Higher Kinded Types (HKTs) also called Higher Kind Polymorphism.

Linq's IEnumerable is a monad, but because of a lack of HKTs it is not possible to maintain the same result type as the input type.

Linq converts your input type to IEnumerable; the monad type, on which you perform all your Linq operations, and then at the end you have to use a toList, toArray, toDictionary, etc. method call to get back to your input type.

The same principle applies to other C# monad types like Task, etc.

I was going to say he was trying to covert a task type rather than the pure data object type that would be the result of the task. That would be why the additional parenthesis of hamsters solution works as it requires the completion of the task prior to the conversion.
 
I was going to say he was trying to covert a task type rather than the pure data object type that would be the result of the task. That would be why the additional parenthesis of hamsters solution works as it requires the completion of the task prior to the conversion.
Correct await is just syntactic sugar for Task. C# in general disguises its monads; whereas F# generally doesn't.

Task is btw just the Future Monad with an API that's different to Linq; but under the covers it employs the same functional algebras.
 
Correct await is just syntactic sugar for Task. C# in general disguises its monads; whereas F# generally doesn't.

Task is btw just the Future Monad with an API that's different to Linq; but under the covers it employs the same functional algebras.

The shorthands are nice but I wonder how many developers bother to read the backing /basis of the obfuscation.

Linq is a great example of that.
 
The shorthands are nice but I wonder how many developers bother to read the backing /basis of the obfuscation.

Linq is a great example of that.
Agreed.

Linq's syntax in the form of:
C#:
var teenStudentsName = from s in studentList
                       where s.age > 12 && s.age < 20
                       select new { StudentName = s.StudentName };
...really pays off when you have data encapsulated in multiple container types that conform to IEnumerable; more specifically any IEnumerable type that has implemented extension methods for Select, SelectMany, etc. ...


The idea for this FYI came originally from Haskell's do notation:
Code:
do { x1 <- action1
   ; x2 <- action2
   ; mk_action3 x1 x2 }


... Scala did something similar with its for comprehensions:
Java:
val asktell = for {
    name <- Ask("What is your name?")
    _    <- Tell(s"Hello ${name}!")
} yield ()


...and in F# its called computation expressions:
C#:
let fetchAndDownload url =
    async {
        let! data = downloadData url
        let processedData = processData data
        do! submitData processData
        ...
    }
 
Last edited:
Doing it a slightly different way I see the data has to be collected first and after the thread has closed, only then can I perform extract actions on the actual data. Still nooooot quite understanding why (aka your description Roi) but I will get there.

C#:
    public async Task<IEnumerable<Student>> GetStudents()
    {
        using (IDbConnection db = Conn)
        {
            db.Open();
            var results = await db.QueryAsync<Student>("spGetStudents", commandType: CommandType.StoredProcedure);
            return results.ToList();
        }
    }
 
Agreed.

Linq's syntax in the form of:
C#:
var teenStudentsName = from s in studentList
                       where s.age > 12 && s.age < 20
                       select new { StudentName = s.StudentName };
...really pays off when you have data encapsulated in multiple container types that conform to IEnumerable; more specifically any IEnumerable type that has implemented extension methods for Select, SelectMany, etc. ...


The idea for this FYI came originally from Haskell's do notation:
Code:
do { x1 <- action1
   ; x2 <- action2
   ; mk_action3 x1 x2 }


... Scala did something similar with its for comprehensions:
Java:
val asktell = for {
    name <- Ask("What is your name?")
    _    <- Tell(s"Hello ${name}!")
} yield ()


...and in F# its called computation expressions:
C#:
let fetchAndDownload url =
    async {
        let! data = downloadData url
        let processedData = processData data
        do! submitData processData
        ...
    }

Lol I was meaning more how Linq can be glorified for and for each loops hidden by syntax. But it's great when querying multiple sources of data that allow the ienumerable interface: xml, file sources, etc.
 
I'm waiting for the Arsenal game to start. So it's either this or WWE which is....ridiculous.

I'll watch WWE if the divas are on.....each other...I mean wrestling each other :unsure:
 
I'll watch WWE if the divas are on.....each other...I mean wrestling each other :unsure:
It's not what it used to be. They're wearing proper outfits now so just lame scripted wrestling and bad acting.
 
Doing it a slightly different way I see the data has to be collected first and after the thread has closed, only then can I perform extract actions on the actual data. Still nooooot quite understanding why (aka your description Roi) but I will get there.

C#:
    public async Task<IEnumerable<Student>> GetStudents()
    {
        using (IDbConnection db = Conn)
        {
            db.Open();
            var results = await db.QueryAsync<Student>("spGetStudents", commandType: CommandType.StoredProcedure);
            return results.ToList();
        }
    }

Async / Await
await basically saves the state at the point its encountered; and essentially packages up the remainder of the code in a continuation block, similar to what can be done with Task's ContinueWith method. await syntactically makes it easy to:
  • enable concurrency,
  • save the state,
  • link a continuation block of code
  • seamlessly unwrap the value held onto by the Task.
i.e. your results variable inside your function is an IEnumerable<...> and not a Task<IEnumerable<...>> .

IEnumerable
IEnumerable as I implied is a transitional container type that wraps a value so that transformation can be performed on the value using functional algebras like Select, Where, SelectMany, etc.

Note:
In a language like Haskell with HKTs there would be no reason to transition from List to IEnumerable and back to List -- because HKTs would allow List to have the same functional power as IEnumerable without having to duplicate all the code it took to enable that functionality for IEnumerable. Think of it in a similar way to how an Abstract class can be used to share code between related classes. Only its more powerful.

IEnumerable's Lazy Computation
It appears however that either you want the result to be either a List<Student> instead of IEnumerable<Student> as your function's signature implies; but you're shooting yourself in the foot with an implicit type conversion fromList<Student> back to IEnumerable<Student> -- the results variable is of type IEnumerable<Student>, which you then convert to List<Student>, and the function's signature Task<IENumerable<Student>> then implicitly converts that back to IEnumerable<Student>.

As I said earlier IEnumerable is designed for Lazy computation as opposed to C#'s default Eager computation. The reason is to help programmer's avoid shooting themselves in the foot with unexpected computation costs.

Essentially all transformations using IEnumerable are composed together without incurring any compute cost when method calls like .Select(x => ....) or .Where(x => ...) are chained together. A Lazy computation doesn't compute the result until you ask it to; e.g. using a ToList() method call.

Here's a simple example that will demonstrate this.
C#:
namespace LazyExample {

  class Program {

    public static int Increment(int x) {
      Console.WriteLine("Incrementing");
      return x + 1;
     }

    public static async Task<IEnumerable<int>> Blah() {
      var result = await Task.Run(() => {
        return Enumerable.Empty<int>().Append(1).Select(Increment);
      });
      return result;
    }

    static async Task Main(string[] args) {
      var output = await Blah();
    }
}

When you run this code, you will see no output; that's because the output variable has received the Lazy IEnumerable.

If I change the code to something similar to yours:
i.e. Adding the ToList() method call.
C#:
namespace LazyExample {

  class Program {

    public static int Increment(int x) {
      Console.WriteLine("Incrementing");
      return x + 1;
     }

    public static async Task<IEnumerable<int>> Blah() {
      var result = await Task.Run(() => {
        return Enumerable.Empty<int>().Append(1).Select(Increment);
      });
      return result.ToList();
    }

    static async Task Main(string[] args) {
      var output = await Blah();
    }
}
When you run this example; you will see the word "Increment" in your console; because the ToList method call forces computation of the lazy computation held onto by IEnumerable. This lazy behaviour is beneficial not only because it avoids costly Big O mistakes by programmers, but equally important it allows the programmer to decide when to incur the cost of compute.

As for your example; I'd suggest either holding onto the Laziness i.e. remove the ToList() and do that later in your code; or change your function's signature to Task<List<Student>> to avoid the implicit and unnecessary conversion back to IEnumerable<Student>.
 
Last edited:
Lol I was meaning more how Linq can be glorified for and for each loops hidden by syntax. But it's great when querying multiple sources of data that allow the ienumerable interface: xml, file sources, etc.
Sure... but its worth it, as its benefits outweigh any perceived obfuscation cost by...
  • making code declarative; focusing more on what you want to achieve as opposed to being bog down with technicalities of how it's achieved.
  • using laziness to make it easier to avoid Big O compute costs for complex transformations
 
Top
Sign up to the MyBroadband newsletter
X