2016-05-28 33 views
6

Volevo creare un metodo che estenda IQueryable dove un utente può specificare in una stringa un nome di proprietà con cui desidera distinguere una raccolta. Voglio usare una logica con un HashSet. Io fondamentalmente voglio emulare questo codice:Creazione di distinzione utilizzando gli alberi di espressione

HashSet<TResult> set = new HashSet<TResult>(); 

foreach(var item in source) 
{ 
    var selectedValue = selector(item); 

    if (set.Add(selectedValue)) 
     yield return item; 
} 

utilizzando alberi di espressione.

Questo è dove ho ottenuto finora:

private Expression AssembleDistinctBlockExpression (IQueryable queryable, string propertyName) 
    { 
     var propInfo = queryable.ElementType.GetProperty(propertyName); 
     if (propInfo == null) 
      throw new ArgumentException(); 

     var loopVar = Expression.Parameter(queryable.ElementType, ""); 
     var selectedValue = Expression.Variable(propInfo.PropertyType, "selectedValue"); 

     var returnListType = typeof(List<>).MakeGenericType(queryable.ElementType); 
     var returnListVar = Expression.Variable(returnListType, "return"); 
     var returnListAssign = Expression.Assign(returnListVar, Expression.Constant(Activator.CreateInstance(typeof(List<>).MakeGenericType(queryable.ElementType)))); 
     var hashSetType = typeof(HashSet<>).MakeGenericType(propInfo.PropertyType); 
     var hashSetVar = Expression.Variable(hashSetType, "set"); 
     var hashSetAssign = Expression.Assign(hashSetVar, Expression.Constant(Activator.CreateInstance(typeof(HashSet<>).MakeGenericType(propInfo.PropertyType)))); 

     var enumeratorVar = Expression.Variable(typeof(IEnumerator<>).MakeGenericType(queryable.ElementType), "enumerator"); 
     var getEnumeratorCall = Expression.Call(queryable.Expression, queryable.GetType().GetTypeInfo().GetDeclaredMethod("GetEnumerator")); 
     var enumeratorAssign = Expression.Assign(enumeratorVar, getEnumeratorCall); 

     var moveNextCall = Expression.Call(enumeratorVar, typeof(IEnumerator).GetMethod("MoveNext")); 

     var breakLabel = Expression.Label("loopBreak"); 

     var loopBlock = Expression.Block(
      new [] { enumeratorVar, hashSetVar, returnListVar }, 
      enumeratorAssign, 
      returnListAssign, 
      hashSetAssign, 
      Expression.TryFinally(
       Expression.Block(
        Expression.Loop(
         Expression.IfThenElse(
         Expression.Equal(moveNextCall, Expression.Constant(true)), 
         Expression.Block(
          new[] { loopVar }, 
          Expression.Assign(loopVar, Expression.Property(enumeratorVar, "Current")), 
          Expression.Assign(selectedValue, Expression.MakeMemberAccess(loopVar, propInfo)), 
          Expression.IfThen(
           Expression.Call(typeof(HashSet<>), "Add", new Type[] { propInfo.PropertyType }, hashSetVar, selectedValue), 
           Expression.Call(typeof(List<>), "Add", new Type[] { queryable.ElementType }, returnListVar, loopVar) 
           ) 
          ), 
         Expression.Break(breakLabel) 
         ), 
        breakLabel 
        ), 
        Expression.Return(breakLabel, returnListVar) 
       ), 
       Expression.Block(
        Expression.Call(enumeratorVar, typeof(IDisposable).GetMethod("Dispose")) 
       ) 
      ) 
     ); 
     return loopBlock; 
    } 

ottengo un'eccezione quando Expression.Block viene chiamato per una variabile loopBlock che va in questo modo:

Nessun metodo 'Add' esiste sul tipo 'System.Collections.Generic.HashSet`1 [T]'.

+1

Penso che si sta creando la HashSet con il tipo sbagliato. Stai creando con il tipo di 'item', non il tipo di' selectedValue'. A proposito, è davvero difficile lavorare con il tuo codice. Prova a fornire un esempio di codice che possiamo compilare ed eseguire. –

+0

Non è sfortunatamente :(Okay, presto modificherò il codice EDIT: Dovrei facilmente testarlo ora –

+0

Probabilmente è più semplice generare il delegato del selettore invece dell'intero metodo distinto Fyi. – usr

risposta

5

Il Expression.Call method overload che si sta utilizzando è per metodi statici.

Citando dal riferimento di cui sopra:

Crea un MethodCallExpression che rappresenta una chiamata a uno statico (Shared in Visual Basic) metodo chiamando il metodo factory appropriato.

Quello che devi fare è utilizzare an overload of that method that is for calling instance methods.

Ecco come la parte rilevante del codice sarà simile:

Expression.IfThen(
    Expression.Call(hashSetVar, "Add", new Type[] { }, selectedValue), 
    Expression.Call(returnListVar, "Add", new Type[] { }, loopVar)) 

Notate come ora passiamo l'istanza (espressione) che abbiamo bisogno di richiamare nel primo parametro di Expression.Call.

Si noti inoltre che si passa un elenco di parametri di tipo vuoto. La ragione di ciò è che il metodo Add in questa classe non ha parametri di tipo. Il parametro tipo T in HashSet<T> e List<T> è definito a livello di classe, non a livello di metodo.

Si avrebbe bisogno di specificare i parametri di tipo solo se sono definite sul metodo stesso come questo:

void SomeMethod<T1>(...