I have a generic class that offers common methods for predicates of concrete types (I use PredicateBuilder
extention methods for that)
public class GenericPredicate<TItem>
{
protected GenericPredicate(Expression<Func<TItem, bool>> predicate)
{
Predicate = predicate;
}
protected GenericPredicate()
{
}
public Expression<Func<TItem, bool>> Predicate { get; protected set; }
//Combines two predicates
public TPred And<TPred>(TPred second) where TPred : GenericPredicate<TItem>, new()
{
return new TPred { Predicate = Predicate.And(second.Predicate) };
}
//Negates the predicate
public TPred Not<TPred>() where TPred : GenericPredicate<TItem>, new()
{
return new TPred { Predicate = Predicate.Not() };
}
}
And concrete predicates:
public class PersonPredicate : GenericPredicate<Person>
{
protected PersonPredicate(Expression<Func<Person, bool>> predicate)
: base(predicate)
{
}
public PersonPredicate()
{
}
public static PersonPredicate IsAdult()
{
return new PersonPredicate(p => p.Age >= 18);
}
public static PersonPredicate IsFemale()
{
return new PersonPredicate(p => Equals(p.Gender, Gender.Female));
}
}
In the factory where I instantiate the concrete predicates I get the error
The type arguments for method Not cannot be inferred from the usage
when calling the generic Not()
PersonPredicate isFemale = PersonPredicate.IsFemale();
PersonPredicate isAdult = PersonPredicate.IsAdult();
PersonPredicate femaleAndAdult = isFemale.And(isAdult);
PersonPredicate notFemale = isFemale.Not(); //Error as described
PersonPredicate notFemaleWorkaround = isFemale.Not<PersonPredicate>(); // Works as suggested by compiler
So, the compiler doesn't know what TItem
is. What makes me confused is that the generic And()
method works without specifying the type arguments explicitly
Basically, type inference for generic type parameters works based on the arguments you pass to the method.
So And()
works, because you're passing in an isAdult
. The compiler infers that TPred
is PersonPredict
as that's the type of isAdult
and there are no other constraints.
For Not()
, you're not passing in any arguments, so the compiler has no information about what you want TPred
to be. That's what it can't infer - not TItem
.
If you really wanted this to work, you could declare GenericPredicate
with two type parameters, one of which is expected to be the subclass itself
public class GenericPredicate<TPred, TItem>
where TPred : GenericPredicate<TPred, TItem>, new()
and:
public class PersonPredicate : GenericPredicate<PersonPredicate, Person>
You might want to have GenericPredicate<TPred,TItem>
as a subclass of GenericPredicate<TItem>
, so that other code could still accept just a GenericPredicate<TItem>
. However, at this point it's all pretty convoluted - you may well better off just specifying the type argument.
See more on this question at Stackoverflow