Reputation: 1251
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
Upvotes: 1
Views: 2054
Reputation: 1500065
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.
Upvotes: 1
Reputation: 7271
In both your examples, the compiler tries to determine the type of predicate TPred
that should be used to resolve the function.
In this example:
isFemale.And(isAdult);
the compiler sees that a PersonPredicate
called isAdult
has been passed in to the function. Thus, it can determine that in the function call, TPred
is actually PersonPredicate
.
In the other example,
isFemale.Not();
the compiler has no hints to understand what fits into TPred
. Hence it needs specific instructions to resolve the conflict.
isFemale.Not<PersonPredicate>();
Now the compiler can resolve the call correctly.
Finally, you may think that because you are assigning the result of the function call to a PersonPredicate
, the compiler should have picked this up. Unfortunately, function target resolution does not consider the assigned value. This is because there are other factors which come into play when you assign values using =
. There may be implicit type casts defined, the operator may have been overloaded, or there may be an implicit constructor. So there is no guarantee that the assignee will be the same type as the actual value.
Upvotes: 1