Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to check if T is a list of objects in a generic method

Tags:

c#

.net

generics

I am trying to write a generic method for deserializing from json file(s) with Json.NET
I want to be able to support deserializing files that contain objects and also arrays of objects. Below are simplified versions of the two generic methods I have at the moment:

/// <summary>   Deserializes object or an array of objects from a list of files. </summary>
public static List<T> Deserialize<T>(List<string> filePathsList)
{
    var result = new List<T>();
    foreach (var file in filePathsList)
        // HOW DO I FIND OUT IF T IS A LIST HERE?
        // Resharper says: the given expression is never of the provided type
        if (typeof(T) is List<object>)
        {
            var deserialized = Deserialize<List<T>>(file);
            result.AddRange(deserialized);
        }
        // Resharper says: Suspicious type check: there is not type in the solution
        // which is inherited from both 'System.Type' and 'System.Collections.IList'
        else if (typeof(T) is IList)
        {
            // deserializing json file with an array of objects
            var deserialized = Deserialize<List<T>>(file);
            result.AddRange(deserialized);
        }
        else
        {
            // deserializing a single object json file
            var deserialized = Deserialize<T>(file);
            result.Add(deserialized);
        }
    return result;
}


/// <summary>   Deserializes object or an array of objects from a file. </summary>
public static T Deserialize<T>(string filepath)
{
    return JsonConvert.DeserializeObject<T>(File.ReadAllText(filepath));
}

The problem is, I need to know if the T is an object or a List of objects, before deserializing. How do I check for that inside the generic method? Because resharper gives me warnings and I can't seem to figure this out.


Edit: I made a mistake in the example code, as @Medo42 points out, it would call Deserialize() with a List<List<Something>> Also, I should not have included an example involving the json serialization at all, it should have been more generic. The question is about finding out if T is a list of objects.

Thanks in advance for any help.

like image 483
cryodream Avatar asked Dec 19 '15 17:12

cryodream


3 Answers

This is not a complete answer, but it's too long for a comment and might help you understand some of the issues better.

// Resharper says: the given expression is never of the provided type
if (typeof(T) is List<object>)

And Resharper is right. The is operator checks whether the instance on the left is of the type on the right, so in your case it checks if typeof(T) is an instance of List<object>. However, typeof(T) returns a Type instance which represents the type of T. The correct way to check (if you are after the exact type) would be

if (typeof(T) == typeof(List<object>))

But note that this will only apply if T is exactly List<object>. If it is also OK to have a subtype of List<object>, the line would be

if (typeof(List<object>).IsAssignableFrom(typeof(T)))

But your problems don't end there. You seem to assume that List<object> is a supertype of all lists. This is not the case, even if we can assume that we will only ever work with the List<T> implementation for lists. The reson for this is that List<T> is invariant.

Invariance means that a list of cats is not a list of mammals. If this seems counterintuitive, that's because you think of a list as a fixed collection that you want to read from. However, you can also add new items to a C# list, and if you were allowed to treat a List<Cat> as a List<Mammal> you could end up trying to add an elephant to that list, and this would cause no end of confusion to anyone else still holding a reference to that list as a List<Cat>.

For a solution to the type checking problem, I think drf's comment to dotctor's answer is the cleanest way to do what you think you want to do:

typeof(T).GetGenericTypeDefinition() == typeof(List<>)

As a final aside, the following code also looks wonky:

var deserialized = Deserialize<List<T>>(file);

You do this after figuring out that T is really a List<Something>, so you're now trying to deserialize your file as a List<List<Something>>, which is probably not what you want.

like image 180
Medo42 Avatar answered Nov 12 '22 09:11

Medo42


You can check it easily

if (typeof(T).Name == "List`1")
{
    // T is a generic list
}

As mentioned by drf a better way which does not rely on internal implementations is:

if (typeof(T).GetGenericTypeDefinition() == typeof(List<>))
{
    // T is a generic list
}
like image 29
Hamid Pourjam Avatar answered Nov 12 '22 09:11

Hamid Pourjam


You dont need this, you can simplify your code just using LINQ!!!

/// <summary>
///     Deserializes object or an array of objects from a list of files.
/// </summary>
public static List<T> Deserialize<T>(List<string> filePathsList)
{
    return filePathsList
        .Select(System.IO.File.ReadAllText)
        .Select(JsonConvert.DeserializeObject<T>)
        .ToList();
}
like image 22
Alberto Monteiro Avatar answered Nov 12 '22 09:11

Alberto Monteiro