RoadieRich
RoadieRich

Reputation: 6556

Get elements of list of unknown type through Reflection

I have types that looks like the following:

public class Test1
{
    [SubTests]
    List<SubTest1> SubTests { get; set; } = [];

    public void Run()
    {
        foreach(var subtest in SubTests)
        {
             // ...
        }
    }

    public class SubTest1 : ISubTest { }
}
public class Test2
{
    [SubTests]
    List<SubTest2> SubTests { get; set; } = [];

    public void Run()
    {
        foreach(var subtest in SubTests)
        {
             // ...
        }
    }

    public class SubTest2 : ISubTest { }
}

public interface ISubTest { }

I want to use reflection to add the elements of Test.SubTests to a BindingList<ISubTest>, but I don't know the type of the ISubTest at compile time.

I tried getting the list like this:

foreach (var property in type.GetProperties())
{
    if (property.GetCustomAttribute<SubTestsAttribute>() != null)
    {
        var subTests = (IList<ISubStep>)property.GetValue(valueSource);
        return new BindingList<ISubstep>(subTests)
    }
}

But that throws an InvalidCastException because the type of the generic parameter to IList<> is different.

Is there any way to do what I'm trying to do?

Upvotes: 0

Views: 44

Answers (2)

Ivan Petrov
Ivan Petrov

Reputation: 4380

I think you need to manually add elements with foreach. IList<T> is not covariant (no out).

foreach (var property in type.GetProperties()) {
    if (property.GetCustomAttribute<SubTestsAttribute>() != null) {
        var subTests = (IEnumerable<ISubStep>)property.GetValue(valueSource);
        var bindingList= new BindingList<ISubstep>();
        foreach (var element in subTests) {
            bindingList.Add(element);
        }
        return bindingList;
    }
}

EDIT: FWIW, I didn't know this was a duplicate when answering - but thanks for the two downvotes. That definitely should teach me a lesson in what the "consensus" is.

Upvotes: -1

Bisjob
Bisjob

Reputation: 828

This require 2 steps:

First, you need to create the BindingList. For this, you can reuse the same generic argument type:

List<int> myList = [];

// Create the binding list by using the same List<> argument
var argumentType = myList.GetType().GenericTypeArguments[0];
var bindingListType = typeof(BindingList<>).MakeGenericType(argumentType);
var bindingList = Activator.CreateInstance(bindingListType);
// Now you have a BindingList<int> instance

Second step is to populate the list. I don't think the BindingList is contravariant, so I don't think you can cast it to a BidingList<>. But you can use reflexion:

var addMethodInfo = bindingListType.GetMethod("Add");
addMethodInfo.Invoke(bindingList, itemToAdd);

You probably can do both at once by using a different constructor:

var argumentType = myList.GetType().GenericTypeArguments[0];
var bindingListType = typeof(BindingList<>).MakeGenericType(argumentType);
var bindingList = Activator.CreateInstance(bindingListType, subTests);
// The reflexion will look for a constructor that accept one argument of type `subTests.GetType()`

Upvotes: 0

Related Questions