Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Why can't List<int> be converted to TCollection in xunit.net theory?

I am trying to write a generic theory in xunit.net that uses collections via the MemberDataAttribute. Please have a look at the following code:

[Theory]
[MemberData("TestData")]
public void AddRangeTest<T>(List<T> items)
{
    var sut = new MyCustomList<T>();

    sut.AddRange(items);

    Assert.Equal(items, sut);
}

public static readonly IEnumerable<object[]> TestData = new[]
    {
        new object[] { new List<int> { 1, 2, 3 } },
        new object[] { new List<string> { "Foo", "Bar" } }
    };

When I try to execute this theory, the following exceptions are thrown: "System.ArgumentException: Object of type 'List'1[System.String] / List'1[System.Int32]' cannot be converted to type 'List'[System.Object]'." (I shortened and merged the text of both exceptions).

I get that this could maybe be related to the parameter type as it is not directly a generic type but a type that uses a nested generic. Thus I transformed the test in the following way:

[Theory]
[MemberData("TestData")]
public void AddRangeTest2<T, TCollection>(TCollection items)
    where TCollection : IEnumerable<T>
{
    var sut = new MyCustomList<T>();

    sut.AddRange(items);

    Assert.Equal(items, sut);
}

In this case, I introduced a generic called TCollection that is contrained to be an IEnumerable<T> instance. When I execute this test, the run with List<string> works, but the run with List<int> produces the following exception: "System.ArgumentException: GenericArguments[1], 'List'1[System.Int32]', on 'AddRangeTest2' violates the constraint of type 'TCollection'." (Again, I shortened the exception text to the relevant points).

My actual question is: why is it possible to use List<string> but not List<int> in a generic theory? Both of these types satisfy the constraint on the TCollection generic in my opinion.

If I change List<int> to List<object>, then everything works - thus I assume that it has something to do with Value Types / Reference Types and Co- / Contravariance.

I use xunit 2.0.0-rc2 although I also observed this behavior with rc1 - thus I think it is version-independent (and possibly not even a problem of xunit). If you want to have a look at the full source code, you can download it from my dropbox (I created it with VS 2013). Please consider this code to be an MCVE.

Thank you so much in advance for your help!

Edit after this question was closed: my question is not answered by Cannot convert from List<DerivedClass> to List<BaseClass> because I do not use inheritance-based casts here at all - instead I believe that xunit.net uses generics that are resolved via reflection.

like image 804
feO2x Avatar asked Oct 31 '22 09:10

feO2x


1 Answers

Whatever you do, it still boils down to the fact that there is going on IEnumerable<int> to IEnumerable<object> conversion, behind the scenes.

You seem to be under assumption that the generic parameters are filled based on the concrete input given.

That is not the case. The generic type parameters are deduced from the variable that is referenced in MemberData, not the actual content it is holding, thus, your test method will be always called with the next signature:

 AddRangeTest2<object, object[]>(object[] items)

The above signature supports implicitly covariance for reference types (meaning: you can pass string[] to this method, but not int[]).

Covariance for value types is not supported.


This said, it would be cool if xUnit is clever enough to specify the correct generic type arguments, based on the actual input. I don't have experience with xUnit myself, perhaps there is already a way? If not, feature request in codeplex, or make it yourself + pull request ;)
like image 75
Erti-Chris Eelmaa Avatar answered Nov 15 '22 06:11

Erti-Chris Eelmaa