2012-02-08 6 views
7

In C#, se si utilizza Type.GetFields() con un tipo che rappresenta una classe derivata, restituirà a) tutti i campi esplicitamente dichiarati nella classe derivata, b) tutti i campi di supporto delle proprietà automatiche nella classe derivata ec) tutti i campi esplicitamente dichiarati nella classe base.Perché Type.GetFields() non restituisce i campi di supporto in una classe base?

Perché i d) campi di appoggio delle proprietà automatiche nella classe base sono mancanti?

Esempio:

public class Base { 
    public int Foo { get; set; } 
} 
public class Derived : Base { 
    public int Bar { get; set; } 
} 
class Program { 
    static void Main(string[] args) { 
     FieldInfo[] fieldInfos = typeof(Derived).GetFields(
      BindingFlags.Public | BindingFlags.NonPublic | 
      BindingFlags.Instance | BindingFlags.FlattenHierarchy 
     ); 
     foreach(FieldInfo fieldInfo in fieldInfos) { 
      Console.WriteLine(fieldInfo.Name); 
     } 
    } 
} 

Questo mostrerà solo il campo appoggio di bar, non Foo.

risposta

8

Un campo che è un campo di supporto non ha alcuna influenza sulla riflessione. L'unica proprietà rilevante dei campi di supporto è che sono privati.

Le funzioni di riflessione non restituiscono private membri di classi base, anche se si utilizza FlattenHierarchy. Sarà necessario eseguire il ciclo manualmente sulla gerarchia delle classi e richiedere campi privati ​​su ciascuna di esse.

Penso che FlattenHierarchy sia scritto con l'intento di mostrare tutti i membri visibili al codice nella classe che si guarda. Quindi i membri di base possono essere nascosti/ombreggiati dai membri con lo stesso nome in una classe derivata e i membri privati ​​non sono affatto visibili.

+0

FlattenHierarchy ha il seguente commento Specifica che i membri statici pubblici e protetti della gerarchia dovrebbero essere restituiti. I membri statici privati ​​nelle classi ereditate non vengono restituiti. I membri statici includono campi, metodi, eventi e proprietà. I tipi annidati non vengono restituiti. Menziona la parola static qui, che mi fa pensare che non funzionerà per nessuno membri statici – R2D2

1

Grazie a @CodeInChaos per la risposta rapida e completa!

Nel caso in cui qualcun altro si imbatta in questo, ecco una soluzione rapida che segue i campi fino alla classe base più lontana.

/// <summary> 
/// Returns all the fields of a type, working around the fact that reflection 
/// does not return private fields in any other part of the hierarchy than 
/// the exact class GetFields() is called on. 
/// </summary> 
/// <param name="type">Type whose fields will be returned</param> 
/// <param name="bindingFlags">Binding flags to use when querying the fields</param> 
/// <returns>All of the type's fields, including its base types</returns> 
public static FieldInfo[] GetFieldInfosIncludingBaseClasses(
    Type type, BindingFlags bindingFlags 
) { 
    FieldInfo[] fieldInfos = type.GetFields(bindingFlags); 

    // If this class doesn't have a base, don't waste any time 
    if(type.BaseType == typeof(object)) { 
     return fieldInfos; 
    } else { // Otherwise, collect all types up to the furthest base class 
     var fieldInfoList = new List<FieldInfo>(fieldInfos); 
     while(type.BaseType != typeof(object)) { 
      type = type.BaseType; 
      fieldInfos = type.GetFields(bindingFlags); 

      // Look for fields we do not have listed yet and merge them into the main list 
      for(int index = 0; index < fieldInfos.Length; ++index) { 
       bool found = false; 

       for(int searchIndex = 0; searchIndex < fieldInfoList.Count; ++searchIndex) { 
        bool match = 
         (fieldInfoList[searchIndex].DeclaringType == fieldInfos[index].DeclaringType) && 
         (fieldInfoList[searchIndex].Name == fieldInfos[index].Name); 

        if(match) { 
         found = true; 
         break; 
        } 
       } 

       if(!found) { 
        fieldInfoList.Add(fieldInfos[index]); 
       } 
      } 
     } 

     return fieldInfoList.ToArray(); 
    } 
} 

Si noti che sto confrontando manualmente i campi in un ciclo annidato per. Se hai classi profondamente nidificate o classi mostruosamente grandi, sentiti libero di usare un HashSet <>.

MODIFICA: tenere presente che questo non ricerca i tipi più in basso nella catena di ereditarietà. Nel mio caso, so che sono al tipo più derivato quando si chiama il metodo.

5

Ecco una versione rivista utilizzando HashSet:

public static FieldInfo[] GetFieldInfosIncludingBaseClasses(Type type, BindingFlags bindingFlags) 
{ 
    FieldInfo[] fieldInfos = type.GetFields(bindingFlags); 

    // If this class doesn't have a base, don't waste any time 
    if (type.BaseType == typeof(object)) 
    { 
     return fieldInfos; 
    } 
    else 
    { // Otherwise, collect all types up to the furthest base class 
     var currentType = type; 
     var fieldComparer = new FieldInfoComparer(); 
     var fieldInfoList = new HashSet<FieldInfo>(fieldInfos, fieldComparer); 
     while (currentType != typeof(object)) 
     { 
      fieldInfos = currentType.GetFields(bindingFlags); 
      fieldInfoList.UnionWith(fieldInfos); 
      currentType = currentType.BaseType; 
     } 
     return fieldInfoList.ToArray(); 
    } 
} 

private class FieldInfoComparer : IEqualityComparer<FieldInfo> 
{ 
    public bool Equals(FieldInfo x, FieldInfo y) 
    { 
     return x.DeclaringType == y.DeclaringType && x.Name == y.Name; 
    } 

    public int GetHashCode(FieldInfo obj) 
    { 
     return obj.Name.GetHashCode()^obj.DeclaringType.GetHashCode(); 
    } 
} 
+0

"La funzione di esempio di Cygon recupera solo i campi delle classi base se la classe ha classi base! = Oggetto." - Non capisco quello che stai cercando di dire. Ho inizializzato la mia 'Lista ' con i campi dal tipo di partenza, proprio come fai con 'HashSet ', quindi entrambe le soluzioni conterranno anche i campi del tipo iniziale. – Cygon

+0

Altrimenti, ben fatto, specialmente che usi 'UnionWith()', molto più elegante della mia scansione di array. Per quanto riguarda le prestazioni, 'HashSet' non sembra fare molto, l'ho provato con 30 campi in 3 livelli di ereditarietà per 1.000.000 iterazioni, terminando in 7157 ms (List) contro 7160 ms (HashSet). – Cygon

+0

Scusa per la confusione, hai ragione. Potrebbe essere stata un'implementazione intermedia che aveva questa limitazione e ho pensato che fosse tua. Ho rimosso la mia dichiarazione nel testo sopra. – Piper