2015-06-18 10 views
5

Devo creare un metodo per selezionare una proprietà di avvio dalla raccolta con il tipo specificato.Impostare solo il tipo del secondo argomento nel metodo generico

io ho creato il metodo come questo (Ho rimosso alcune parti per la brevità):

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); 
} 

e posso chiamare questo metodo come:

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

// .. initialize personList 

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

tutto funziona bene.

Ma, non voglio impostare il tipo primo argomento come Person, perché il compilatore può dedurre questo tipo di argomento dall'origine della raccolta. C'è un modo per chiamare il metodo del genere:

.SelectFirstPropertyWithType<int>()

Il problema è che ho bisogno T parametro all'interno del mio metodo, e non voglio creare Func con la riflessione in fase di esecuzione.

Grazie.

+0

C'era molte domande simili su questo. La risposta è semplicemente - non puoi fare qualcosa del genere. Compilatore che potrebbe dedurre tutti i tipi o nessuno –

risposta

2

C# Generics semplicemente non consente di specificare un sottoinsieme dei parametri di tipo. È tutto o niente.

Il modo per aggirare questo problema è scrivere un'interfaccia fluente. Rompi questa operazione in una catena di metodi.

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); 
} 

Ora si può chiamare:

personList.AsQueryable() 
     .SelectFirstProperty().OfType<int>() 
     .ToList(); 
+0

Funzionerà con l'implementazione di 'IQueryable ' da Entity Framework? – Dennis

+0

@Dennis - Questo dovrebbe funzionare su qualsiasi framework che fornisce un'implementazione di IQueryable . –

+0

Mi è piaciuto molto questo approccio. Ma devi sostituire "IQueryable " nel metodo di estensione con 'FirstPropertyWithTypeSelector ' –

3

No. Il compilatore deve essere in grado di dedurre tutti i parametri di tipo. Se non può, ti chiederà di specificarli tutti.

Il compilatore non può dirvi che può dedurre il primo o il secondo, quindi invece di avere un'applicazione di compilazione non deterministica, si interrompe.

+0

Grazie per la risposta. –