Mikayil Abdullayev
Mikayil Abdullayev

Reputation: 12376

How to cast PropertyInfo value to ICollection<T> where T might be any class

I'm building my own generic solution to Entity Framework update in disconnected scenario. Many different approaches could be taken but I decided to decorate ICollection properties inside my entities with a custom attribute so that I can check the state of each entity inside those collections. Here's a sample entity with a navigation property:

public class SomeEntity 
{
    public int TaskId{ get; set; }
    public string Instruction { get; set; }
    [EntityNavigationProperty]
    public virtual ICollection<TaskExecutor> Executors { get; set; }
}

public class TaskExecutor 
{
    public int TaskExecutorId { get; set; }
    public int TaskId { get; set; }
    public virtual Task Task { get; set; }
}

public class EntityNavigationProperty : Attribute {}

I have a generic Update method that I'm planning to use to update any type of entity which will ensure that the related entities also get updated properly.

public void Update(TEntity entity)
{
    PropertyInfo[] properties = entity.GetType().GetProperties();
    foreach (PropertyInfo pi in properties)
    {
        if (Attribute.IsDefined(pi, typeof(EntityNavigationProperty)))
        {
            foreach (//here I need to iterate through the ICollection object)
            {

            }
        }
    }
}

Now, assume I'm sending an instnce of Task to the above update method.In line 3, when iterator comes to the Executors property, the condition in line 5 resolves to true. Now I need to iterate through the Executors property and do appropriate tasks. For this particular case, in line 6 I can say:

  foreach (var item in (pi.GetValue(entity) as ICollection<TaskExecutor>))

But how can I determine what to type instead of T in ICollection<T>?

Upvotes: 0

Views: 628

Answers (1)

xanatos
xanatos

Reputation: 111890

The usual solution is:

foreach (object item in (IEnumerable)pi.GetValue(entity)) 
{
}

and then inside check the type of the item.

Note that for historical reasons IEnumerable<T> is based on IEnumerable, and ICollection<T> is based on IEnumerable<T> and so on IEnumerable, but ICollection<T> isn't based on ICollection.

In general if you want the type T of an IEnumerable<T> you can (taken from https://stackoverflow.com/a/906513/613130):

// returns typeof(T) of an IEnumerable<T>,
// or typeof(object) of an IEnumerable.
public static Type GetGenericTOfIEnumerable(object o)
{
    return o.GetType()
            .GetInterfaces()
            .Where(t => t.IsGenericType
                && t.GetGenericTypeDefinition() == typeof(IEnumerable<>))
            .Select(t => t.GetGenericArguments()[0])
            .FirstOrDefault() ?? (o is IEnumerable ? typeof(object) : null);
}

note that with the changes I've introduced I've created some small side-effects: a collection NOT based on IEnumerable<T> but only on IEnumerable will return typeof(object). A collection based on multiple IEnumerable<T> will return only a single one... For example:

public class MyStupidClass : IEnumerable<int>, IEnumerable<long>
{
}

But this is a degenerate example.

Upvotes: 2

Related Questions