Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Why can't the compiler infer the type for this select call?

I'm trying to create a list of ViewModels out of a DTO by calling a select on the list of DTO's. However, the compiler gives me an error saying:

The type arguments for method cannot be inferred from the usage try specifying the type arguments

My question is, why can't it? Both TextSectionDTO and ImageSectionDTO are derived from SectionDTO. I'm trying to create a List of Sections, and both TextSection and ImageSection are derived from Section.

I know this question is close to some other questions posted on here, but I wasn't able to find an answer there.

This is my code:

private List<Section> BuildSectionViewModel(IEnumerable<SectionDTO> ss )
{
    var viewModels = ss.Select((SectionDTO s) =>
    {
        switch (s.SectionType)
        {
            case Enums.SectionTypes.OnlyText:
                return new TextSection((TextSectionDTO) s);
            case Enums.SectionTypes.OnlyImage:
                return new ImageSection((ImageSectionDTO) s);
            default:
                throw new Exception("This section does not exist - FIXME");
        }
    }).ToList();

    return viewModels;
}

When I change the types so that I only accept superclass SectionDTO and only return Section (I make them both normal classes in this scenario) the select works like you'd expect. Then when I change the types to only TextSectionDTO and TextSection (changing the abstracts back), the select doesn't work anymore.

I'd like a solution so that I can get this to work with the construction I have right now, though I'm more interested in why this does not work the way it is. Even if I can get this to work I'll probably refactor this later.

Note:

  • I'm targeting MVC 4.5 (so the compiler is not some old version not being able to infer, which was the solution to some similar questions on here).
  • The switch clause has a default case, i.e. the error shouldn't be able to be caused by a path not returning a value.
like image 861
Glubus Avatar asked Feb 04 '16 11:02

Glubus


2 Answers

case Enums.SectionTypes.OnlyText:
    return new TextSection((TextSectionDTO) s);
case Enums.SectionTypes.OnlyImage:
    return new ImageSection((ImageSectionDTO) s);

These two cases return differen types. The compiler isn´t smart enough to check if those types derive from the same base-type so you have to cast them explicitely:

case Enums.SectionTypes.OnlyText:
    return (SectionDTO) new TextSection((TextSectionDTO) s);
case Enums.SectionTypes.OnlyImage:
    return (SectionDTO) new ImageSection((ImageSectionDTO) s);

Why this isn´t implemented on the compiler? I assume this is because the compiler has to check for many different types. Assume that your two types Foo1 and Foo2 do not directly derive from Bar but from two different ones (Bar1 and Bar2 accordingly) that themselfs inherit from Bar. Now the compiler should check if Foo1 and Foo2 can be assigned to any common base-class which they can not, and also check if they derive from something that HAS a common base-class (Bar). In the end we had to check the whole inheritance-chain until object, not mentioning any interfaces which should also be checked.

class Foo1 : Bar1 {}
class Foo2 : Bar2 {}

class Bar1 : Bar {}
class Bar2 : Bar {} 
like image 102
MakePeaceGreatAgain Avatar answered Sep 22 '22 17:09

MakePeaceGreatAgain


Select method has two type aguments - TSource and TResult. Since you are invoking it on IEnumerable<SectionDTO>, TSource is inferred to be SectionDTO, so ss.Select((SectionDTO s) => is not needed and can be just ss.Select(s => ....

The problem is the TResult which in your case cannot be inferred. Why? You have two returns - TextSection and ImageSection. They are not the same and none of them is a base of the another. What do you think the compiler should infer? You think that the answer should be the common base type Section, but the same could apply to a common base object or any common base class/interface of the two type. In other words, the result is ambiguous, so instead of guessing what is your intention, the compiler requires you to specify that explicitly. Similar to ternary operator ? : it would be sufficient to specify the common base in just one branch, so the following should resolve it

var viewModels = ss.Select(s =>
{
    switch (s.SectionType)
    {
        case Enums.SectionTypes.OnlyText:
            return (Section)new TextSection((TextSectionDTO) s);
        case Enums.SectionTypes.OnlyImage:
            return new ImageSection((ImageSectionDTO) s);
        default:
            throw new Exception("This section does not exist - FIXME");
    }
}).ToList();
like image 34
Ivan Stoev Avatar answered Sep 20 '22 17:09

Ivan Stoev