2012-02-08 7 views
7

En C#, si usa Type.GetFields() con un tipo que representa una clase derivada, devolverá a) todos los campos declarados explícitamente en la clase derivada, b) todos los campos de respaldo de propiedades automáticas en la clase derivada yc) todos los campos explícitamente declarados en la clase base.¿Por qué Type.GetFields() no devuelve campos de respaldo en una clase base?

¿Por qué faltan los campos de respaldo d) de propiedades automáticas en la clase base?

Ejemplo:

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

Esto mostrará sólo el campo respaldo de Bar, no Foo.

Respuesta

8

Un campo que es un campo de respaldo no influye en la reflexión. La única propiedad relevante de los campos de respaldo es que son privados.

Las funciones de reflexión no devuelven miembros privados de clases base, incluso si usa FlattenHierarchy. Deberá realizar un ciclo manualmente sobre su jerarquía de clases y solicitar campos privados en cada una.

Creo que FlattenHierarchy está escrito con la intención de mostrar todos los miembros visibles para codificar en la clase que miras. Por lo tanto, los miembros de la base pueden ser ocultados/sombreados por miembros con el mismo nombre en una clase más derivada y los miembros privados no son visibles en absoluto.

+0

FlattenHierarchy tiene el siguiente comentario Especifica que los miembros estáticos públicos y protegidos por la jerarquía deben ser devueltos . Los miembros estáticos privados en clases heredadas no se devuelven. Los miembros estáticos incluyen campos, métodos, eventos y propiedades. Los tipos anidados no se devuelven. Aquí se menciona la palabra estática, que me hace pensar que no funcionará para ninguno de los miembros estáticos – R2D2

1

¡Gracias a @CodeInChaos por la respuesta rápida y completa!

En caso de que alguien más tropiece con esto, aquí hay una solución rápida que sigue los campos hasta la clase base más lejana.

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

Tenga en cuenta que estoy comparando manualmente los campos en un ciclo for anidado. Si tiene clases profundamente anidadas o monstruosamente grandes, siéntase libre de usar un HashSet <>.

EDITAR: También tenga en cuenta que esto no busca tipos más abajo en la cadena de herencia. En mi caso, sé que soy del tipo más derivado al llamar al método.

5

Aquí es una versión revisada usando 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 función de muestra de Cygon solo recupera campos de clases base si la clase tiene clases base! = Objeto." - No entiendo lo que intentas decir. Inicialicé mi 'Lista ' con los campos del tipo inicial, tal como lo hace con 'HashSet ', por lo que ambas soluciones también contendrán los campos del tipo inicial. – Cygon

+0

De lo contrario, muy bien hecho, especialmente que utiliza 'UnionWith()', mucho más elegante que mi exploración de matriz. En cuanto a rendimiento, el 'HashSet' no parece hacer mucho, lo intenté con 30 campos en 3 niveles de herencia para 1,000,000 iteraciones, terminando en 7157 ms (Lista) vs 7160 ms (HashSet). – Cygon

+0

Disculpa la confusión, tienes razón. Pudo haber sido una implementación intermedia que tenía esa limitación y supuse que era tuya. Eliminé mi declaración en el texto anterior. – Piper

Cuestiones relacionadas