Search code examples
c#reflectiontypespropertyinfodynamictype

Dynamically modifying an IEnumerable property via reflection


I have a variety of classes that have various properties that implement IEnumerable (eg IEnumerable<string>, IEnumerable<bool>, IEnumerable<enum>, etc). I'm trying to write some code to filter down the values of theses properties (eg if the value is { "one", "two", "three" } I might want to filter where .Contains("t")).

Here's the essence of what I've got:

class MyObject
{
    public IEnumerable<string> stringProp { get; set; } = new[] { "one", "two", "three" };
    public IEnumerable<bool> boolProp { get; set; } = new[] { true, false, true };
    public IEnumerable<int> intProp { get; set; } = new[] { 1, 2, 3 };
}

public static void Main(string[] args)
{
    MyObject obj = new MyObject();
    
    foreach (PropertyInfo prop in typeof(MyObject).GetProperties())
    {               
        prop.SetValue(obj, (prop.GetValue(obj) as IEnumerable<dynamic>).Where(val => val != null));
    }
}

The problem is that when I try to set the value back to the object (property.SetValue) an error is thrown because the new value is an IEnumerable<object>.

Object of type 'System.Linq.Enumerable+WhereArrayIterator`1[System.Object]' cannot be converted to type 'System.Collections.Generic.IEnumerable`1[System.String]'

I've tried Convert.ChangeType but that does not work because IEnumerable does not implement IConvertible.

How might I accomplish this? Why does the LINQ Where query change the IEnumerable<dynamic> into IEnumerable<object>?


Solution

  • Did I understand correctly? Are you looking for something like this?

    var obj = new MyObject();
    
    foreach (var prop in typeof(MyObject).GetProperties())
    {
        //assumming all things are IEnumerable<something>
        var type = prop.PropertyType.GenericTypeArguments[0];
    
        //We can't "instantiate" something as ephemeral as an IEnumerable,
        //so we need something more concrete like a List
        //There might be other ways to filter - this seemed to be the easiest
        var listType = typeof(List<>).MakeGenericType(type);
        var instance = (IList)Activator.CreateInstance(listType);
        
        var currentEnum = (IEnumerable)prop.GetValue(obj);
        foreach (var item in currentEnum)
        {
             if (item != default) // != null would be silly for booleans and ints
             {
                 instance.Add(item);
             }
        }
    
        prop.SetValue(obj, instance);
    }
    

    Summary: Generics and the dynamic keyword don't usually mix this way - having a dynamic generic argument makes no sense. Think of dynamic as something that actually means "object" but also lets you write whatever you like against it. And of course, IEnumerable<object> is probably better off as IEnumerable. And for generics with multiple parameters, you're better off with object or even better a specific class.