When to explicitly specify type arguments in generic C# method calls?

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

Jon Skeet
people
quotationmark

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.

people

See more on this question at Stackoverflow