WesFanMan
WesFanMan

Reputation: 123

Complex IEnumerable Result from LINQ Select

I'm trying to understand the resultant type from a LINQ select action. I'm having trouble understanding how to utilize the result.

Consider the following:

public class ReportContract
{
    public int ContractId { get; set; }
    public string ContractName { get; set; }
    public string Title { get; set; }
    public string[] Details { get; set; }
}

public class ReportContractVM
{
    public int ContactId { get; set; }
    public string ContractName { get; set; }
}

public class SomeController : ControllerBase
{
    public ActionResult<IEnumerable<ReportContractVM>> Get()
    {
        IEnumerable<ReportContract> contracts = CreateConrtacts();

        IEnumerable<ReportContractVM> result = contracts.Select(
            x => new ReportContractVM
                     {
                       ContactId = x.ContractId,
                       ContractName = x.ContractName
                     }
            );

        return Ok(result);
    }

    private IEnumerable<ReportContract> CreateContracts()
    {
        List<ReportContract> contracts = new List<ReportContract>()
        {
            new ReportContract { 
               ContractId = 1234, 
               ContractName= "ABCD", 
               Details= new string[] { 
                 "test", "Best", "rest", "jest"}, 
               Title="First" 
            },
            new ReportContract { 
               ContractId = 1235, 
               ContractName= "EFGH", 
               Details= new string[] { 
                 "fish", "dish", "wish", "pish"}, 
               Title="Second" 
            },
            new ReportContract { 
               ContractId = 1236, 
               ContractName= "IJKL", 
               Details= new string[] { 
                 "hot", "tot", "mot", "jot"}, 
               Title="Third" 
            },
            new ReportContract { 
               ContractId = 1237, 
               ContractName= "MNOP", 
               Details= new string[] { 
                 "ring", "sing", "bing", "ping"}, 
               Title="Fourth" 
            }
        };

        return contracts;
    }
}

Inspecting the type assigned to 'result' in the debugger yields:

System.Linq.Enumberable.SelectListIterator
< 
  ComplexIEnumerable.ReportContract, 
  ComplexIEnumerable.ReportContractVM 
>

I am not sure how this relates to

IEnumerable<ReportContractVM>.

How is

IEnumerable<ReportContractVM> 

accessed from this result?

Please ignore the rest of this text. The "intelligent" editor is demanding more detail and I think this suffices.

Upvotes: 1

Views: 2987

Answers (2)

Dai
Dai

Reputation: 155370

I recommend reading this article that explains what Enumerator (aka yield return methods) materialization is - as it's core to how Linq works and how to use IEnumerable<T> results (note that an IEnumerable<T> can refer to a both materialized collections like T[] or List<T> and also non-materialized enumerators):

https://learn.microsoft.com/en-us/dotnet/csharp/programming-guide/concepts/linq/intermediate-materialization

As for your case, generally speaking, never return a non-materialized Entity Framework (i.e. database-backed) IEnumerable<T> or IQueryable<T> from an ASP.NET Web API Controller or pass a non-materialized IEnumerable<T> as a member of a ViewModel (this is because the lifetime of the parent DbContext may be shorter than the lifetime of the IEnumerable<T>/IQueryable<T> object). However returning an non-materialized collection using only in-memory objects is fine (such as an enumerator over enum values to get SelectListItem objects):

Specifically, change this:

IEnumerable<ReportContractVM> result = contracts.Select(
            x => new ReportContractVM
                     {
                       ContactId = x.ContractId,
                       ContractName = x.ContractName
                     }
            );

to this:

List<ReportContractVM> list = contracts
    .Select( x => new ReportContractVM { ContractId = x.ContractId, ContractName = x.ContractName  } )
    .ToList();

Also, change your ActionResult<T> to use IReadOnlyList<T> instead of IEnumerable<T> which will help ensure you always return a materialized list instead of a possibly non-materialized enumerator:

public class SomeController : ControllerBase
{
    public ActionResult<IReadOnlyList<ReportContractVM>> Get()
    {
    }
}

BTW, I've been having problems with ActionResult<T> not working well with generics and in async Task<ActionResult<T>> actions so I personally prefer to do this, but YMMV:

public class SomeController : ControllerBase
{
    [Produces( typeof( IReadOnlyList<ReportContractVM> ) )
    public IActionResult Get()
    {
    }

    // if async:

    [Produces( typeof( IReadOnlyList<ReportContractVM> ) )
    public async Task<IActionResult> Get()
    {
    }
}

Upvotes: 3

danielm
danielm

Reputation: 339

Basically the output is the concrete enumerable type returned from Select with type parameters referencing both the input and output types. It may be easier to see in a simplified example below.

namespace ExampleNamespace
{
    class A
    {
        public A(int i) { }
    }
}

Now say I have this code somewhere:

List<int> i = new List<int>() { 1, 2, 3, 4, 5, 6 };
var temp = i.Select(x => new A(x));

Upon inspection the type of temp is:

System.Linq.Enumerable.WhereSelectListIterator<int, ExampleNamespace.A>

So to break this down:

  • System.Linq.Enumerable.WhereSelectListIterator is the type of iterator returned from Select. This differs from your type slightly probably due to different .NET versions.

  • int is the the type that your starting IEnumerable held

  • ExampleNamespace.A is the full name of the output type.

That all being said, you should read and understand what Dai posted in their answer.

Upvotes: 0

Related Questions