Gaugo
Gaugo

Reputation: 11

How can I use Where<T> extension method when T is unknown at compile time

I’m programming C# MVC application using JSON and try to retrieve the JSON back to the DB using EF.

Unfortunatly, I connot make such code working:

IEnumerable<object> NewValueSubCollection = (IEnumerable<object>)NewValue.GetType().GetProperty(col.Name).GetValue(NewValue, null);
//Where col is a property of an object of type (IEnumerable<???>)

foreach (var line in ((IEnumerable<object>)col.GetValue(originalEntity, null)).ToList<object>())
{
    Type lineType = ObjectContext.GetObjectType(line.GetType());
    var lineEntitySet = context.GetEntitySet(lineType);
    EntityKey lineKey = context.CreateEntityKey(lineEntitySet.Name, line);
    if (NewValueSubCollection.Where(lineKey).Count() == 0) //See bellow
    context.DeleteObject(line);
}

Where I’ve implemented this:

public static class LinqExtension
{
    //Enable searching in a collection is an entity Key exist... and retrieve info.
    public static IEnumerable<T> Where<T>(this IEnumerable<T> source,EntityKey key)
    {
        return Enumerable.Where(source, CreateFilterExpressionBasedOnKey<T>(key).Compile());
    }

    private static Expression<Func<TInput, bool>> CreateFilterExpressionBasedOnKey<TInput>(Type sourceType, EntityKey key)
    {
        var param = Expression.Parameter(sourceType, "");

        Expression myFilter = Expression.Constant(true);
        if (key != null)
        {
            foreach (var item in key.EntityKeyValues)
            {
                Expression Left = Expression.PropertyOrField(param, item.Key.ToString());
                Expression Right = Expression.Constant(item.Value);

                myFilter = Expression.AndAlso(myFilter, Expression.Equal(Left, Right));
            }
        }
        return Expression.Lambda<Func<TInput, bool>>(myFilter, param);
    }
}

The problem is that NewValueSubCollection is of type: IEnumerable<object> when I wish I could pass IEnumerable<MyListObjectType> through my Where extension…. Then I’ve got an exception at runtime: System.ArgumentException: 'TOId' is not a member of type 'System.Object'

If I Use:

Type generic = typeof(Collection<>);
Type typeArgs = (NewValue.GetType().GetProperty(col.Name).GetValue(NewValue, null)).GetType().GetGenericArguments()[0];
Type constructed = generic.MakeGenericType(typeArgs);
object o = Activator.CreateInstance(constructed);
o = NewValue.GetType().GetProperty(col.Name).GetValue(NewValue, null);

I get a compilation error with following code:

if (o.Where(lineKey).Count() == 0) //error!!!
// 'object' does not contain a definition for 'Where' acception a first argument of type 'object' could be found (are you missing directive or an assembly reference?)

Upvotes: 1

Views: 717

Answers (2)

Julien Ch.
Julien Ch.

Reputation: 1261

In your first example you NewValueSubCollection is statically type as IEnumerable<object>, so your parametrized type T is object, and when you compile your expression it tries to find the property on the object class, thus the exception.

In the same way your o.Where(...) cannot work because the Where extension method is defined on IEnumerable<>, and the methods are resolved statically at compile time.

I'm afraid you'll have to invoke the Where method by reflection, you might find some help here: How do I invoke an extension method using reflection?

Upvotes: 0

Dan Bryant
Dan Bryant

Reputation: 27515

You'll need to access this dynamically, but you can avoid some of the headache of reflection using the 'dynamic' keyword.

So this:

NewValueSubCollection.Where(lineKey).Count() == 0

Becomes this:

NewValueSubCollection.Cast<dynamic>().Where(d => lineKey(d)).Count() == 0

Or (simpler and more efficient, if enumeration has no side effects) this:

!NewValueSubCollection.Cast<dynamic>().Any(d => lineKey(d))

A simple proof of concept program:

class Program
{
    static void Main(string[] args)
    {
        var list = new List<string>();
        IEnumerable<object> objects = list;
        Func<string, bool> func = s => s.Contains("x");

        list.Add("Test");
        list.Add("x");
        list.Add("mux");
        foreach (var item in objects.Cast<dynamic>().Where(d => func(d)))
            Console.WriteLine(item);
        Console.ReadKey();
    }
}

Upvotes: 1

Related Questions