Search code examples
c#genericsreflection

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;
    }

Solution

  • 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());