2

I have the following extension methods:

public static class QueryableOptionalDateRangeExtensions
{
    public static IQueryable<T> StartsFrom<T>(this IQueryable<T> query, DateTime date)
        where T : IOptionalDateRange // Might also be IRequiredDateRange
    {
        return query.Where(obj => obj.Start >= date);
    }

    public static IQueryable<T> StartsUntil<T>(this IQueryable<T> query, DateTime date)
        where T : IOptionalDateRange // Might also be IRequiredDateRange
    {
        return query.Where(obj => obj.Start < date);
    }

    public static IQueryable<T> EndsUntil<T>(this IQueryable<T> query, DateTime date)
        where T : IOptionalDateRange
    {
        return query.Where(obj => obj.End <= date);
    }
}

public static class QueryableRequiredDateRangeExtensions
{
    public static IQueryable<T> StartsFrom<T>(this IQueryable<T> query, DateTime date)
        where T : IRequiredDateRange
    {
        return query.Where(obj => obj.Start >= date);
    }

    public static IQueryable<T> StartsUntil<T>(this IQueryable<T> query, DateTime date)
        where T : IRequiredDateRange
    {
        return query.Where(obj => obj.Start < date);
    }

    public static IQueryable<T> EndsUntil<T>(this IQueryable<T> query, DateTime date)
        where T : IRequiredDateRange
    {
        return query.Where(obj => obj.End <= date);
    }
}

This however does not work since it can't infer the overloading from the type of T for some reason (although it seems possible to me).
What can be done to work around this issue?

EDIT:
Here's the IDateRange interface:

public interface IDateRange<TStart, TEnd>
{
    TStart Start { get; set; }
    TEnd End { get; set; }
}

It just specifies that the class has a start and an end. Now I want it to specify whether the object has an optional date range (both start and end are nullable) or a required date range (both are value types) but the same extension method must work on both and I don't really want to specify the Start and End properties' types.

the_drow
  • 18,571
  • 25
  • 126
  • 193
  • @Daniel Hilgarth: Already done, but IDateRange has two generic parameters for the Start & End properties (they might be nullable or not) and then you can't compare T to datetime. – the_drow Sep 15 '11 at 12:30
  • Please post it as a comment to my answer - I will answer your comment there. – Daniel Hilgarth Sep 15 '11 at 12:31
  • You should think about that design. Your `IDateRange` says it is a date range, still, I could put anything in there, e.g. I could create an `IDateRange`... – Daniel Hilgarth Sep 15 '11 at 12:38
  • You still haven't shown what's actually happening. A short but *complete* piece of code showing a single method declaration (rather than 4) and something trying to use it would be helpful. – Jon Skeet Sep 15 '11 at 12:39
  • Surely the property types must be DateTime ? You are comparing them to DateTime in the extension method body so why would you allow other types? – Peter Kelly Sep 15 '11 at 12:41
  • @Peter yes, but the date time can be nullable as well. – the_drow Sep 15 '11 at 12:42
  • 1
    Okay, to me that says the design needs to be re-thought (see my updated answer). Opening it up to any types just to support optional or required (a yes/no flag) doesn't feel right. – Peter Kelly Sep 15 '11 at 13:12
  • @Peter: Exactly. That's what I was trying to say earlier... – Daniel Hilgarth Sep 16 '11 at 07:05

5 Answers5

3

It's not clear what you mean by "does not work". Were you expecting overload resolution (including finding extension methods) to take account of the constraints? If so, that doesn't happen - I've written a blog post going into the details of this.

That's possibly why what you're trying isn't working - although as you haven't given an example, it's hard to say for sure. As for how to fix it - I would suggest using different method names, e.g. MaybeStartsFrom for the optional range. If you could give a short but complete example of what you're trying to achieve, that would help...

Sunny Patel
  • 7,830
  • 2
  • 31
  • 46
Jon Skeet
  • 1,421,763
  • 867
  • 9,128
  • 9,194
2

Make both IRequiredDateRange and IOptionalDateRange inherit from IDateRange and put the Start and End properties there. What you are trying can't be achieved otherwise, because "generic method type inference deliberately does not make any deductions from the constraints", see here.

Community
  • 1
  • 1
Daniel Hilgarth
  • 171,043
  • 40
  • 335
  • 443
0

You could move them into separate namespaces but that would be BAD. It is fragile because changing namespaces could result in different behaviour (unexpectedly). Plus this won't work if you need to use both methods in the same file (as pointed out by Daniel).


Okay, so after reading your update why don't you add an IsDateOptional property to your IDateRange interface, define the date types as DateTime getting rid of the generic element to that interface. There is now no need for two separate interfaces IOptionalDateRange and IRequiredDateRange... and you can replace the two extension classes with one.

    public interface IDateRange
    {
        DateTime Start { get; set; }
        DateTime End { get; set; }
        bool IsDateOptional { get; }
    }

    public static class QueryableDateRangeExtensions
    {
        public static IQueryable<T> StartsFrom<T>(this IQueryable<T> query, DateTime date)
            where T : IDateRange
        {
            return query.Where(obj => obj.Start >= date);
        }

        public static IQueryable<T> StartsUntil<T>(this IQueryable<T> query, DateTime date)
            where T : IDateRange
        {
            return query.Where(obj => obj.Start < date);
        }

        public static IQueryable<T> EndsUntil<T>(this IQueryable<T> query, DateTime date)
            where T : IDateRange
        {
            return query.Where(obj => obj.End <= date);
        }
    }
Peter Kelly
  • 14,253
  • 6
  • 54
  • 63
0

First, it T is always "IOptionalDateRange" in the methods of QueryableOptionalDateRangeExtensions, why make it generic in the first place?

Second, it seems the IOptionalDateRange interface simply extends the IRequiredDateRange interface (I guessed that because your wrote IOptionalDateRange might also be IRequiredDateRange). That means, if an object implements IOptionalDateRange, it also implements IRequiredDateRange. How do you expect the compiler to differentiate?

So solve this, the interface should not inherit each other, instead they could share a common base class.

codymanix
  • 28,510
  • 21
  • 92
  • 151
  • Because T must be the concrete type in order to be used in IQueryable. Both IOptionalDateRange and IRequiredDateRange inherit from IDateRange. I wrote that the constraint matches both IRequiredDateRange and IOptionalDateRange. – the_drow Sep 15 '11 at 13:06
0

If you are on .NET 4 and implement the interfaces with classes (not structures), you can take advantage of covariance and make the methods non-generic. Instead you would have

IQueryable<IOptionalDateRange> EndsUntil(this IQueryable<IOptionalDateRange> query, DateTime date)
IQueryable<IRequiredDateRange> EndsUntil(this IQueryable<IRequiredDateRange> query, DateTime date)
Gideon Engelberth
  • 6,095
  • 1
  • 21
  • 22
  • That would be the accepted answer if I were on .NET 4. However I'm not sure how NHibernate would take this. I believe that it will still cause an error. – the_drow Sep 15 '11 at 13:21