Convert List to typeof UnderlyingSystemType

540 Views Asked by At

I am currently working on code that is using dynamic-linq, I ran into a problem when using a List<BaseClass>, where the list actually contains a list of the Person Class.

When I execute the following code I get a ParseException:

var list = new List<BaseClass>();

list.Add(new Person
{
  FirstName = "Joe",
  Surname   = "Bloggs"
});

list.Where("FirstName == @0", "Joe");

And the Exception:

enter image description here

Please see BaseClass below:

public class BaseClass 
{
    public int Id { get; set; }
}

And the Person class:

public class Person : BaseClass
{
   public string FirstName { get; set; }
   public string Surname   { get; set; }
}

I can overcome the error by implementing the following code:

var list = new List<BaseClass>();

list.Add(new Person
{
  FirstName = "Joe",
  Surname   = "Bloggs"
});            

var newList = CreateListOfCorrectType<BaseClass>(list);
newList.Where("FirstName == @0", "Joe");

Please see CreateListOfCorrectType<T> method below:

private IList CreateListOfCorrectType<T>(
         List<T> list) 
{
  if (list.Count == 0)
  {
    return list;
  }

  var typeInfo = list.FirstOrDefault().GetType();

  var correctListType   = typeof(List<>).MakeGenericType(typeInfo.UnderlyingSystemType);
  var listOfCorrectType = (Activator.CreateInstance(correctListType)) as IList;

  list.ForEach(x => listOfCorrectType.Add(x));

  return listOfCorrectType;
}

My Question is if using the CreateListOfCorrectType is the best way of overcoming the issue? and if not what alternatives do I have in getting the List<BaseClass> to the correct Type.

I am looking to use this with existing code, and changing the existing List<>types are not possible. And the CreateListOfCorrectType method is not aware of the Person class.

Please note that class names and variables are for demonstrative purposes only.

UPDATE

Optimist's answer below leaded me to the solution for my issue, please see extension method used below:

public static IList ToDerivedListType(this IList list)
    {
        if (list == null || list.Count == 0) 
        {
            return list;
        }

        var type       = list.Cast<object>().FirstOrDefault().GetType();
        var castedList = typeof(Enumerable).GetMethod("Cast", System.Reflection.BindingFlags.Static | System.Reflection.BindingFlags.Public)
                                           .MakeGenericMethod(type)
                                           .Invoke(null, new[] { list });

        return typeof(Enumerable).GetMethod("ToList", System.Reflection.BindingFlags.Static | System.Reflection.BindingFlags.Public)
                                 .MakeGenericMethod(type)
                                 .Invoke(null, new[] { castedList }) as IList;           
    }

System.Linq.Enumerable.Cast and MakeGenericMethod was the key.

2

There are 2 best solutions below

1
On BEST ANSWER

depending on the actual use cases, some things can be improved:

  • runtime expenses: the CreateListOfCorrectType function copies all elements into a new collection which results in unnecessary expense in case only a subset is taken out of the returned collection .
  • scope of applicability of the mapping function: this could be widened to work with IEnumerable.
  • finally, both the mapping and the filtering function can be combined into one to reduce the amount of code in consuming functions.

System.Linq.Enumerable.Cast and MakeGenericMethod can be employed to achieve that:

static public class Extension
{
    static public IEnumerable CastDynamic(this  IEnumerable Source, Type Type)
    {
        return
            (IEnumerable)
            typeof(Enumerable)
            .GetMethod("Cast", System.Reflection.BindingFlags.Static | System.Reflection.BindingFlags.Public)
            .MakeGenericMethod(Type)
            .Invoke(null, new[] { Source });
    }

    static public IEnumerable CastToFirstType(this  IEnumerable Source)
    {
        if (0 == Source.Take(1).Count())
        {
            return Source;
        }

        return CastDynamic(Source, Source.Cast<object>().FirstOrDefault().GetType());
    }

    static public IEnumerable WhereCastToFirstType(this IEnumerable Source, string Predicate, params object[] values)
    {
        return Source.CastToFirstType().Where(Predicate, values);
    }
}

If tried to mimic the function you showed in terms of exceptions thrown. There is a difference because the casting is done after the mapping function returns.

4
On

How about using the OfType linq method:

list.OfType<Person>().Where("FirstName == @0", "Joe");

See https://msdn.microsoft.com/en-us/library/vstudio/bb360913(v=vs.100).aspx