RAM
RAM

Reputation: 485

Can I specify 'T' when invoking at generic method at runtime (probably using reflection)?

I have a number of JSON files each of which contains a list of different class objects.

I can work out the data class I'm expecting from the file's name and I have a generic JSON file reader which returns the correct data. However, I can't figure out how to assign that to anything in the calling method that allows me to access the results as the actual type.

I'm currently using this line:

var k = ReadJson<dynamic>(filePath, typeof(List<>).MakeGenericType(new[] { type }));

OR

var k = ReadJson<Object>(filePath, typeof(List<>).MakeGenericType(new[] { type }));

but they're both giving me

Unable to cast object of type 'System.Collections.Generic.List`1[Abc.Shared.Models.Location]' to type 'System.Collections.Generic.List`1[System.Object]'.

Conjecture: Since I can't call ReadJson directly can I call the ReadJson via reflection specifying the type of the generic 'T'? I've not been able work out if that's possible.

(For completeness here's the generic method - I don't think the problem is here though)

    public static List<T> ReadJson<T>(string filePath, Type t) where T : class, new()
    {
        List<T> regions = new List<T>();

        using (StreamReader file = System.IO.File.OpenText(filePath))
        {
            JsonSerializer serializer = new JsonSerializer();
            regions = (List<T>)serializer.Deserialize(file, t);
        }

        return regions;
    }

Upvotes: 0

Views: 70

Answers (2)

xanatos
xanatos

Reputation: 111920

You can't cast List<T> to List<TBase> (and object is base to all the reference types), because List<T> isn't covariant. You can cast IEnumerable<T> to IEnumerable<TBase>, bacause IEnumerable<T> is covariant. For historical reasons even arrays are covariant, so you can cast T[] to TBase[]. I'll give you both samples:

public static IEnumerable<T> ReadJson1<T>(string filePath, Type t) where T : class, new()
{
    IEnumerable<T> regions = new List<T>();

    using (StreamReader file = System.IO.File.OpenText(filePath))
    {
        JsonSerializer serializer = new JsonSerializer();
        regions = (IEnumerable<T>)serializer.Deserialize(file, t);
    }

    return regions;
}

public static T[] ReadJson2<T>(string filePath, Type t) where T : class, new()
{
    using (StreamReader file = System.IO.File.OpenText(filePath))
    {
        JsonSerializer serializer = new JsonSerializer();
        var regions = (T[])serializer.Deserialize(file, t);
        return regions;
    }
}

Type type = typeof(MyObject);
IEnumerable<object> k1 = ReadJson1<object>("test.json", typeof(List<>).MakeGenericType(new[] { type }));
object[] k2 = ReadJson2<object>("test.json", type.MakeArrayType());

Upvotes: 1

Jes&#250;s L&#243;pez
Jes&#250;s L&#243;pez

Reputation: 9241

Try the following:

public static List<T> ReadJson<T>(string filePath)
{
    using (var reader = File.OpenText(filePath))
    {
        var serializer = new JsonSerializer();
        return (List<T>) serializer.Deserialize(reader, typeof(List<T>));
    }
}

public static IList ReadJson(string filePath, Type itemType)
{
    using (var reader = File.OpenText(filePath))
    {
        var serializer = new JsonSerializer();
        var type = typeof(List<>).MakeGenericType(itemType);
        return (IList)serializer.Deserialize(reader, type);
    }
}

If you know the item type at design time you can use the first version. If you don't know the item type at design time, but you know it at runtime, you can use the second version. In any case both methods returns a list of itemType/T items.

The second versions is declared to return IList. This is all we can do since we don't know the item type at design time. However it returns a real List<T>.

Your ReadJson method is redundant, if you know T then you know t, because t = typeof(T). Or perhaps you mean t = typeof(List<T>)

Upvotes: 0

Related Questions