Set only second argument type in generic method

147 views Asked by At

I have to create a method for selecting a firts property from collection with the specified type.

I have created the method like this (I have removed some parts for the brevity):

public static IQueryable<TResult> SelectFirstPropertyWithType<T, TResult>(this IQueryable<T> source)
{
    // Get the first property which has the TResult type
    var propertyName = typeof(T).GetProperties()
        .Where(x => x.PropertyType == typeof(TResult))
        .Select(x => x.Name)
        .FirstOrDefault();

    var parameter = Expression.Parameter(typeof(T));
    var body = Expression.Convert(Expression.PropertyOrField(parameter, propertyName), typeof(TResult));
    var expression = Expression.Lambda<Func<T, TResult>>(body, parameter);

    return source.Select(expression);
}

And I can call this method as:

List<Person> personList = new List<Person>();

// .. initialize personList

personList.AsQueryable()
          .SelectFirstPropertyWithType<Person, int>()
          .ToList();

Everything works fine.

But, I don't want to set first argument type as Person, because compiler can infer this argument type fromthe source of the collection. Is there any way to call the method like that:

.SelectFirstPropertyWithType<int>()

The problem is I need T parameter inside my method, and I don't want to create Func with reflection in runtime.

Thanks.

2

There are 2 answers

5
Andrew Shepherd On BEST ANSWER

C# Generics simply does not allow you to specify a subset of the type parameters. It's all or nothing.

The way to work around this is to write a fluent interface. You break this operation up into a chain of methods.

 public class FirstPropertyWithTypeSelector<T>
 {
     private readonly IQueryable<T> _source;

     public FirstPropertyWithTypeSelector(IQueryable<T> source)
     {
        _source = source;
     }

     public IQueryable<TResult> OfType<TResult>() 
     {
         // Get the first property which has the TResult type
          var propertyName = typeof(T).GetProperties()
             .Where(x => x.PropertyType == typeof(TResult))
             .Select(x => x.Name)
             .FirstOrDefault();
         var parameter = Expression.Parameter(typeof(T));
         var body = Expression.Convert(Expression.PropertyOrField(parameter, propertyName), typeof(TResult));
          var expression = Expression.Lambda<Func<T, TResult>>(body, parameter);
         return _source.Select(expression);
     }
 }

 public static FirstPropertyWithTypeSelector<T> SelectFirstProperty(this IQueryable<T> source)
 {
    return new FirstPropertyWithTypeSelector<T>(source);
 }

Now you can call:

  personList.AsQueryable()
      .SelectFirstProperty().OfType<int>()
      .ToList();
0
Patrick Hofman On

No. The compiler should be able to infer all type parameters. If it can't it will demand you to specify all of them.

The compiler can't tell you it can infer the first or the second, so instead of having a non deterministic compiling application, it just breaks.