2

I have created a method with two generic parameters where one parameter (itemsToAdd) must be the same type as the generic parameter of the next parameter (inputList).

See this demo code:

public class GenericsDemo
{
    public void AddToList<TList, TItems>(TList inputList, params TItems[] itemsToAdd)
        where TItems : IConvertible
        where TList : IEnumerable<TItems>

    {
        IEnumerable<IConvertible> someOtherList;

        // Sounds good, doesn't work..
        //someOtherList = inputList;

        // This works
        someOtherList = (IEnumerable<IConvertible>)inputList;
    }
}

I would expect the inputList can be directly assigned into the IEnumerable<IConvertible> someOtherList, but it needs a cast. Why the cast is needed?

Chris
  • 27,210
  • 6
  • 71
  • 92
chviLadislav
  • 1,204
  • 13
  • 15
  • https://learn.microsoft.com/en-us/dotnet/standard/generics/covariance-and-contravariance –  Jul 10 '17 at 10:55
  • 1
    `IList` is invariant so an `IList` is not an `IList`. – Lee Jul 10 '17 at 10:57
  • I just found the `someOtherList = (IList)inputList;` will throw runtime exception, even if I changed the `TList` constraint to `IEnumerable` which is covariant – chviLadislav Jul 10 '17 at 11:19
  • You should change your target (`IList`) to `IEnumerable`. Changing the source compile time type is not improving anything. – grek40 Jul 10 '17 at 11:20
  • @grek40, yes, you are right, I didn't realized the `IList` is invariant.. anyway it will throw runtime exception with the `IEnumerable` – chviLadislav Jul 10 '17 at 11:24
  • sorry, it will not throw the exception, I had there one place where I did not changed it.. I will edit the question to make it clear.. – chviLadislav Jul 10 '17 at 11:29
  • Now the question is correct.. – chviLadislav Jul 10 '17 at 11:36
  • 1
    You need to add a `class` constraint to `TItems` for `someOtherList = inputList` to compile. Variance is not supported when the element type is a struct. – Lee Jul 10 '17 at 11:38
  • @chviLadislav: I'm a little confused as to whether there is actually a question here or not? Your question is "I would expect the inputList can be directly assigned into the IList someOtherList, but it needs a cast. Why the cast is needed?" but you have no instances of `IList` in your question. Did you just edit the actual problem out of your question? If so the question makes no sense any more (because it isn't a question). Your question isn't *supposed* to be correct. It is supposed to show a problem and the answer shows the solution. – Chris Jul 10 '17 at 11:39
  • @Chris, I made a mistake with the `IList`, now after the edit it should be clear (`IEnumerable` is correct of course). The question is still the same - why the cast is needed? Why the compiler complains if the cast is not in place? From my point of view, it is type safe to do the commented line `someOtherList = inputList;` – chviLadislav Jul 10 '17 at 11:44
  • @Lee, thanks, that's the answer! I need to add the class constraint.. – chviLadislav Jul 10 '17 at 11:54
  • @chviLadislav: In that case you should edit the bit I quoted to refer to `IEnumerable` rather than `IList` as well. Otherwise your question makes no sense due to not using `IList`anywhere. And also you don't need the "EDIT" section saying what you've edited - if you want to record that sort of thing then the edit page has a space for reasons - what your question used to say is not relevant to people coming to it now and just increases confusion. – Chris Jul 10 '17 at 11:54
  • 1
    @Chris, this is my first question here. the next time it will be better:) Thanks for your comments, I didn't know the edits can be seen.. – chviLadislav Jul 10 '17 at 12:17

1 Answers1

4

Covariance only works for classes, not for structs (Source).

Thus, if you restrict TItems to reference types, your code compiles (fiddle):

    where TItems : class, IConvertible
Heinzi
  • 167,459
  • 57
  • 363
  • 519