vojta
vojta

Reputation: 5651

Reference to a property of class in predicate using reflection

I have a simple class with many DateTime properties:

public class MyClass
{
    public DateTime Created {get; set;}
    public DateTime LastChanged {get; set;}
    public DateTime LastAccessed {get; set;}
    ...
}

Later, somewhere in my code I would like to filter collection of MyClass based on these properties. For each property I would like to do something like this:

myQueryableOfMyClass = myQueryableOfMyClass.Where(a => ((begin == null) || (a.Created >= begin)) && ((end == null) || (a.Created <= end));

If I had to do this for each of my DateTime property, it would be a lot of code with some risk of a typo, so I would like to do something like this:

myQueryableOfMyClass = Filter(myQueryableOfMyClass, begin, end, MyClass.Created);
myQueryableOfMyClass = Filter(myQueryableOfMyClass, changebegin, changeend, MyClass.LastChanged);
myQueryableOfMyClass = Filter(myQueryableOfMyClass, accbegin, accend, MyClass.LastAccessed);
...

where the Filter method is implemented using the LINQ Where as in the first example.

The code above does not compile, of course, because MyClass.Created is a nonsense.

There must be some solution using reflection, but I have little very experience with reflection. Could you please help me?

Upvotes: 1

Views: 322

Answers (3)

Tomas Elison
Tomas Elison

Reputation: 31

I am a big fan of readable code where method names explain what they do. For that reason I would suggest adding the following methods. They could be either added in the data class itself as in my example or they could be in a "Filter" class.

public class MyClass
{
    public DateTime Created { get; set; }
    public DateTime LastChanged { get; set; }
    public DateTime LastAccessed { get; set; }

    public bool WasCreatedInTimespan(DateTime? begin, DateTime? end)
    {
        return IsDateInTimespan(Created, begin, end);
    }

    public bool WasChangedInTimespan(DateTime? begin, DateTime? end)
    {
        return IsDateInTimespan(LastChanged, begin, end);
    }

    public bool WasAccessedInTimespan(DateTime? begin, DateTime? end)
    {
        return IsDateInTimespan(LastAccessed, begin, end);
    }

    private static bool IsDateInTimespan(DateTime date, DateTime? begin, DateTime? end)
    {
        return (!begin.HasValue || date >= begin.Value) && (!end.HasValue || date >= end.Value);
    }
}

With these methods, the calling code becomes very clear. Like so:

var createdList = myQueryableOfMyClass.Where(m => m.WasCreatedInTimespan(begin, end));
var changedList = myQueryableOfMyClass.Where(m => m.WasChangedInTimespan(begin, end));
var accessedList = myQueryableOfMyClass.Where(m => m.WasAccessedInTimespan(begin, end));

Upvotes: 3

jdweng
jdweng

Reputation: 34421

Create a helper method

        public void test()
        {
           myQueryableOfMyClass = myQueryableOfMyClass.Where(a => Filter(begin, end, a));
        }



        public Boolean Filter(DateTime begin, DateTime end, DateTime date)
        {
            if(((begin == null) || (date >= begin)) && ((end == null) || (date <= end)))
            {
                return true;
            }
            else
            {
                return false;
            }
        }
    }​

Upvotes: 1

Matthew Watson
Matthew Watson

Reputation: 109567

You could use a Func<MyClass, DateTime> to choose which property to use, like so:

public static IEnumerable<MyClass> Filter
(
    IEnumerable<MyClass> myClasses, 
    DateTime? begin, 
    DateTime? end, 
    Func<MyClass, DateTime> selector
)
{
    return myClasses.Where(a => ((begin == null) || (selector(a) >= begin)) && ((end == null) || (selector(a)) <= end));
}

You'd call it like this:

Filter(myClasses, begin, end, myClass => myClass.Created);

Where you would replace myClass => myClass.Created with a selector for the property you want to use.

Upvotes: 4

Related Questions