2010-08-23 10 views
6

Possiedo un numero di raccolte IEnumerable il cui numero esatto e tipi è soggetto a frequenti modifiche (a causa della generazione automatica del codice).Come richiamare System.Linq.Enumerable.Count <> su IEnumerable <T> utilizzando Reflection?

Sembra qualcosa di simile a questo:

public class MyCollections { 
    public System.Collections.Generic.IEnumerable<SomeType> SomeTypeCollection; 
    public System.Collections.Generic.IEnumerable<OtherType> OtherTypeCollection; 
    ... 

In fase di esecuzione che voglio per determinare ogni tipo ed è contare senza dover riscrivere il codice dopo ogni generazione del codice. Quindi sto cercando un approccio generico usando la riflessione. Il risultato che sto cercando è qualcosa di simile:

MyType: 23 
OtherType: 42 

Il mio problema è che io non riesco a capire come richiamare il metodo Count correttamente. Ecco quello che ho finora:

 // Handle to the Count method of System.Linq.Enumerable 
     MethodInfo countMethodInfo = typeof(System.Linq.Enumerable).GetMethod("Count", new Type[] { typeof(IEnumerable<>) }); 

     PropertyInfo[] properties = typeof(MyCollections).GetProperties(); 
     foreach (PropertyInfo property in properties) 
     { 
      Type propertyType = property.PropertyType; 
      if (propertyType.IsGenericType) 
      { 
       Type genericType = propertyType.GetGenericTypeDefinition(); 
       if (genericType == typeof(IEnumerable<>)) 
       { 
        // access the collection property 
        object collection = property.GetValue(someInstanceOfMyCollections, null); 

        // access the type of the generic collection 
        Type genericArgument = propertyType.GetGenericArguments()[0]; 

        // make a generic method call for System.Linq.Enumerable.Count<> for the type of this collection 
        MethodInfo localCountMethodInfo = countMethodInfo.MakeGenericMethod(genericArgument); 

        // invoke Count method (this fails) 
        object count = localCountMethodInfo.Invoke(collection, null); 

        System.Diagnostics.Debug.WriteLine("{0}: {1}", genericArgument.Name, count); 
       } 
      } 
     } 
+0

"MyCollections" è un tipo di una variabile/campo/proprietà? Sembra che tu stia usando entrambi. –

+0

Il tuo numero MethodInfo-reference è null. Risolto il problema e il codice probabilmente funzionerebbe. –

+0

Siamo spiacenti, l'esempio indicato non è accurato. L'ho risolto – mbi

risposta

3

Se ti ostini a farlo nel modo più duro; p

Modifiche:

  • come ottenere countMethodInfo per un metodo generico
  • le argomentazioni per invocare
Codice

(nota obj è la mia istanza di MyCollections) :

MethodInfo countMethodInfo = typeof (System.Linq.Enumerable).GetMethods().Single(
     method => method.Name == "Count" && method.IsStatic && method.GetParameters().Length == 1); 

    PropertyInfo[] properties = typeof(MyCollections).GetProperties(); 
    foreach (PropertyInfo property in properties) 
    { 
     Type propertyType = property.PropertyType; 
     if (propertyType.IsGenericType) 
     { 
      Type genericType = propertyType.GetGenericTypeDefinition(); 
      if (genericType == typeof(IEnumerable<>)) 
      { 
       // access the collection property 
       object collection = property.GetValue(obj, null); 

       // access the type of the generic collection 
       Type genericArgument = propertyType.GetGenericArguments()[0]; 

       // make a generic method call for System.Linq.Enumerable.Count<> for the type of this collection 
       MethodInfo localCountMethodInfo = countMethodInfo.MakeGenericMethod(genericArgument); 

       // invoke Count method (this fails) 
       object count = localCountMethodInfo.Invoke(null, new object[] {collection}); 

       System.Diagnostics.Debug.WriteLine("{0}: {1}", genericArgument.Name, count); 
      } 
     } 
    } 
+0

Grazie mille! Dopo aver esaminato la tua risposta, ho capito in parte il mio errore. In effetti, solo gli argomenti su Invoke dovevano essere adattati (ovviamente!) E quindi tutta la mia implementazione originale funzionava come previsto. Anche il tuo approccio Linq al recupero di countMethodInfo sembra sexy, mi sembra un po 'complicato per me ;-) – mbi

+0

@embee - quando ho provato il tuo codice originale, ho ottenuto 'null' per il metodoinfo ... –

+0

funziona bene per silverlight/windows phone – mbi

2

che sta andando a coinvolgere un po 'MakeGenericMethod - e un sacco di riflessione generalmente. Personalmente, sarei tentato di semplificare semplicemente l'ammaraggio, i farmaci generici, in questo caso:

public static int Count(IEnumerable data) { 
    ICollection list = data as ICollection; 
    if(list != null) return list.Count; 
    int count = 0; 
    IEnumerator iter = data.GetEnumerator(); 
    using(iter as IDisposable) { 
     while(iter.MoveNext()) count++; 
    } 
    return count; 
} 

È possibile lanciare il non generico IEnumerable banalmente, anche se il recupero attraverso la riflessione.

+0

Certo, questa è una bella idea. Ma sto cercando la soluzione al mio difetto in MakeGenericMethod. – mbi

+0

Non possiamo semplicemente 'foreach' su' data', o abbiamo bisogno di trattare l'enumeratore in modo esplicito? – AakashM

+0

@embee - 2 secondi, cercherò ... –

1
var count = System.Linq.Enumerable.Count(theCollection); 

Edit: si dice che è generato, però, così si può non solo creare una proprietà con le chiamate a Count()?

public class MyCollections 
{ 
    public System.Collections.Generic.IEnumerable<SomeType> SomeTypeCollection; 
    public System.Collections.Generic.IEnumerable<OtherType> OtherTypeCollection; 

    public int CountSomeTypeCollection 
    { 
     get { return this.SomeTypeCollection.Count(); } 
    } 

    ... 
+0

Siamo spiacenti, ho perso il fatto che non si conosce 'T' in fase di compilazione. –

+0

MyCollections viene generato automaticamente e sovrascritto abbastanza spesso. Questo è il motivo per cui devo usare il riflesso dall'esterno. So che è difficile ma dovrebbe essere possibile ... – mbi

1

Ormai, la questione è stata risolta, ma vorrei presentarvi un assettato giù — e penso, piuttosto banale versione — del "chiamare un metodo di estensione generico", che può essere utilizzato per richiamare Count riflessivo:

// get Enumerable (which holds the extension methods) 
Type enumerableT = typeof(Enumerable); 

// get the Count-method (there are only two, you can check the parameter-count as in above 
// to be certain. Here we know it's the first, so I use the first: 
MemberInfo member = enumerableT.GetMember("Count")[0]; 

// create the generic method (instead of int, replace with typeof(yourtype) in your code) 
MethodInfo method = ((MethodInfo) member).MakeGenericMethod(typeof(int)); 

// invoke now becomes trivial 
int count = (int)method.Invoke(null, new object[] { yourcollection }); 

I lavori di cui sopra, in quanto non è necessario utilizzare il tipo generico di IEnumerable<> per poter invocare Count, che è un'estensione di Enumerable e prende un argomento di IEnumerable<T> come primo parametro (è un'estensione), ma non è necessario specificarlo.

Nota che, dalla lettura della tua domanda, mi sembra che dovresti effettivamente utilizzare i generici per i tuoi tipi, che aggiungono la sicurezza di tipo al tuo progetto e ti consentono comunque di usare Count o qualsiasi altra cosa. Dopo tutto, l'unica cosa certa è che sono tutti Enumerable, giusto? Se è così, chi ha bisogno di riflessione?

+0

Il motivo della riflessione è semplice: ciò che etichettate come "yourtype" non è noto al momento della compilazione (o almeno cambia frequentemente). – mbi

+0

@embee: questo, ho capito. Ma se cambia frequentemente (viene generato) è l'ideale per i generici. Ma in tutta onestà, non conosco abbastanza la tua situazione per sapere con certezza se il tuo approccio è buono considerando. – Abel

+0

Probabilmente non capisco abbastanza generici per seguire il tuo suggerimento, mi dispiace. In questo scenario MyCollections è qualcosa che devi dare per scontato. – mbi

Problemi correlati