reflections life examples reflection

life - reflection wikipedia



¿Cómo podría la Reflexión no conducir a olores de código? (18)

Como se mencionó anteriormente, la reflexión se usa principalmente para implementar código que necesita tratar con objetos arbitrarios. Los mapeadores de ORM, por ejemplo, necesitan crear instancias de objetos de clases definidas por el usuario y llenarlas con valores de las filas de la base de datos. La forma más simple de lograr esto es a través de la reflexión.

En realidad, tienes razón en parte, la reflexión a menudo es un olor a código. La mayoría de las veces trabajas con tus clases y no necesitas reflexión: si conoces tus tipos, probablemente estés sacrificando la seguridad de tipo, el rendimiento, la legibilidad y todo lo que es bueno en este mundo, innecesariamente. Sin embargo, si está escribiendo bibliotecas, marcos o utilidades genéricas, probablemente se encontrará con situaciones que se manejan mejor con la reflexión.

Esto está en Java, que es con lo que estoy familiarizado. Otros idiomas ofrecen cosas que se pueden usar para lograr los mismos objetivos, pero en Java, la reflexión tiene aplicaciones claras para las cuales es la mejor (y en ocasiones, la única) solución.

Vengo de lenguajes de bajo nivel: C ++ es el programa de mayor nivel I en.

Recientemente me encontré con Reflection, y simplemente no puedo entender cómo podría usarse sin códigos.

La idea de inspeccionar una clase / método / función durante el tiempo de ejecución, en mi opinión, apunta a un defecto en el diseño. Creo que la mayoría de los problemas que Reflection (tries) resuelve pueden usarse con Polimorfismo o con el uso apropiado de la herencia.

¿Me equivoco? ¿No entiendo mal el concepto y la utilidad de Reflection?

Estoy buscando una buena explicación de cuándo utilizar Reflection donde otras soluciones fallarán o serán demasiado engorrosas para implementarlas, así como cuándo NO usarlas.

Por favor ilumine este bajo nivel de lubber.


Con la reflexión, puede escribir una pequeña cantidad de código independiente del dominio que no necesita cambiar a menudo en lugar de escribir mucho más código dependiente del dominio que necesita cambiar más frecuentemente (como cuando se agregan / eliminan propiedades). Con las convenciones establecidas en su proyecto, puede realizar funciones comunes basadas en la presencia de ciertas propiedades, atributos, etc. La transformación de datos de objetos entre diferentes dominios es un ejemplo donde la reflexión realmente es útil.

O un ejemplo más simple dentro de un dominio, donde quiere transformar datos de la base de datos a objetos de datos sin necesidad de modificar el código de transformación cuando cambian las propiedades, siempre que se mantengan las convenciones (en este caso, coincidan nombres de propiedad y un atributo específico) :

///-------------------------------------------------------------------------------- /// <summary>Transform data from the input data reader into the output object. Each /// element to be transformed must have the DataElement attribute associated with /// it.</summary> /// /// <param name="inputReader">The database reader with the input data.</param> /// <param name="outputObject">The output object to be populated with the input data.</param> /// <param name="filterElements">Data elements to filter out of the transformation.</param> ///-------------------------------------------------------------------------------- public static void TransformDataFromDbReader(DbDataReader inputReader, IDataObject outputObject, NameObjectCollection filterElements) { try { // add all public properties with the DataElement attribute to the output object foreach (PropertyInfo loopInfo in outputObject.GetType().GetProperties()) { foreach (object loopAttribute in loopInfo.GetCustomAttributes(true)) { if (loopAttribute is DataElementAttribute) { // get name of property to transform string transformName = DataHelper.GetString(((DataElementAttribute)loopAttribute).ElementName).Trim().ToLower(); if (transformName == String.Empty) { transformName = loopInfo.Name.Trim().ToLower(); } // do transform if not in filter field list if (filterElements == null || DataHelper.GetString(filterElements[transformName]) == String.Empty) { for (int i = 0; i < inputReader.FieldCount; i++) { if (inputReader.GetName(i).Trim().ToLower() == transformName) { // set value, based on system type loopInfo.SetValue(outputObject, DataHelper.GetValueFromSystemType(inputReader[i], loopInfo.PropertyType.UnderlyingSystemType.FullName, false), null); } } } } } } // add all fields with the DataElement attribute to the output object foreach (FieldInfo loopInfo in outputObject.GetType().GetFields(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.GetField | BindingFlags.Instance)) { foreach (object loopAttribute in loopInfo.GetCustomAttributes(true)) { if (loopAttribute is DataElementAttribute) { // get name of field to transform string transformName = DataHelper.GetString(((DataElementAttribute)loopAttribute).ElementName).Trim().ToLower(); if (transformName == String.Empty) { transformName = loopInfo.Name.Trim().ToLower(); } // do transform if not in filter field list if (filterElements == null || DataHelper.GetString(filterElements[transformName]) == String.Empty) { for (int i = 0; i < inputReader.FieldCount; i++) { if (inputReader.GetName(i).Trim().ToLower() == transformName) { // set value, based on system type loopInfo.SetValue(outputObject, DataHelper.GetValueFromSystemType(inputReader[i], loopInfo.FieldType.UnderlyingSystemType.FullName, false)); } } } } } } } catch (Exception ex) { bool reThrow = ExceptionHandler.HandleException(ex); if (reThrow) throw; } }


Creo que la reflexión es uno de estos mecanismos que son poderosos pero que pueden abusarse fácilmente. Se le proporcionan las herramientas para convertirse en un "usuario avanzado" para fines muy específicos, pero no está destinado a reemplazar el diseño orientado a objetos adecuado (así como el diseño orientado a objetos no es una solución para todo) o para ser utilizado a la ligera.

Debido a la forma en que está estructurado Java, ya está pagando el precio de representar su jerarquía de clases en la memoria en tiempo de ejecución (compare con C ++ donde no paga ningún costo a menos que use cosas como métodos virtuales). Por lo tanto, no hay un costo racional para bloquearlo completamente.

La reflexión es útil para cosas como la serialización: cosas como Hibernate o digester pueden usarlo para determinar la mejor manera de almacenar objetos automáticamente. De manera similar, el modelo JavaBeans se basa en nombres de métodos (una decisión cuestionable, lo admito), pero debe poder inspeccionar qué propiedades están disponibles para construir cosas como editores visuales. En versiones más recientes de Java, las reflexiones son las que hacen que las anotaciones sean útiles: puede escribir herramientas y hacer metaprogramación utilizando estas entidades que existen en el código fuente pero que pueden accederse en tiempo de ejecución.

Es posible atravesar una carrera completa como programador de Java y nunca tener que usar la reflexión porque los problemas que maneja no lo requieren. Por otro lado, para ciertos problemas, es bastante necesario.


Daré un ejemplo de la solución de CA que me dieron cuando comencé a aprender.

Contenía clases marcadas con el atributo [Ejercicio], cada clase contenía métodos que no se implementaron (arrojando NotImplementedException). La solución también tenía pruebas unitarias que fallaron todas.

El objetivo era implementar todos los métodos y pasar todas las pruebas unitarias.

La solución también tenía una interfaz de usuario que leería toda la clase marcada con Ejercicio y usaría la reflexión para generar una interfaz de usuario.

Posteriormente, se nos pidió que implementaramos nuestros propios métodos, y más tarde aún entendiéramos cómo se cambió la interfaz de usuario "mágicamente" para incluir todos los nuevos métodos que implementamos.

Extremadamente útil, pero a menudo no bien entendido.


Ejemplo muy simple en Python. Supongamos que tiene una clase que tiene 3 métodos:

class SomeClass(object): def methodA(self): # some code def methodB(self): # some code def methodC(self): # some code

Ahora, en alguna otra clase, desea decorar esos métodos con algún comportamiento adicional (es decir, desea que esa clase imite SomeClass, pero con una funcionalidad adicional). Esto es tan simple como:

class SomeOtherClass(object): def __getattr__(self, attr_name): # do something nice and then call method that caller requested getattr(self.someclass_instance, attr_name)()


El software y los marcos de pruebas unitarias como NUnit usan el reflejo para obtener una lista de pruebas para ejecutar y ejecutar. Encuentran todas las suites de prueba en un módulo / ensamblado / binario (en C # estas están representadas por clases) y todas las pruebas en esas suites (en C # estos son métodos en una clase). NUnit también le permite marcar una prueba con una excepción esperada en caso de que esté probando contratos de excepción.

Sin reflexión, tendría que especificar de alguna manera qué suites de prueba están disponibles y qué pruebas están disponibles en cada suite. Además, cosas como excepciones deberían probarse manualmente. Los marcos de pruebas de unidades de C ++ que he visto usaron macros para hacer esto, pero algunas cosas siguen siendo manuales y este diseño es restrictivo.


En realidad, ya estás usando un sistema reflectivo todos los días : tu computadora.

Claro, en lugar de clases, métodos y objetos, tiene programas y archivos. Los programas crean y modifican archivos al igual que los métodos crean y modifican objetos. Pero luego los programas son archivos en sí mismos, ¡y algunos programas incluso inspeccionan o crean otros programas!

Entonces, ¿por qué está tan bien que una instalación de Linux sea reflexiva y que nadie siquiera lo piense, y atemorizante para los programas OO?


Es muy útil para la inyección de dependencia. Puede explorar tipos de conjuntos cargados que implementan una interfaz determinada con un atributo determinado. Combinado con los archivos de configuración adecuados, demuestra ser una forma muy poderosa y limpia de agregar nuevas clases heredadas sin modificar el código del cliente.

Además, si está haciendo un editor que realmente no se preocupa por el modelo subyacente, sino más bien en la forma en que los objetos se estructuran directamente, ala System.Forms.PropertyGrid )


He visto buenos usos con atributos personalizados. Tal como un marco de base de datos.

[DatabaseColumn("UserID")] [PrimaryKey] public Int32 UserID { get; set; }

La reflexión se puede usar para obtener más información sobre estos campos. Estoy bastante seguro de que LINQ to SQL hace algo similar ...

Otros ejemplos incluyen marcos de prueba ...

[Test] public void TestSomething() { Assert.AreEqual(5, 10); }


La idea detrás de esto era poder consultar las propiedades de objetos de GUI, para proporcionarlos en una GUI para personalizarlos y preconfigurarlos. Ahora sus usos se han extendido y demostrado ser factibles.

EDITAR: ortografía


La reflexión se utiliza con mayor frecuencia para eludir el sistema de tipo estático, sin embargo, también tiene algunos casos de uso interesantes:

¡Escribamos un ORM!

Si está familiarizado con NHibernate o con la mayoría de los otros ORM, escriba clases que se correlacionen con tablas en su base de datos, algo como esto:

// used to hook into the ORMs innards public class ActiveRecordBase { public void Save(); } public class User : ActiveRecordBase { public int ID { get; set; } public string UserName { get; set; } // ... }

¿Cómo crees que está escrito el método Save() ? Bueno, en la mayoría de los ORM, el método Save no sabe qué campos están en las clases derivadas, pero puede acceder a ellos utilizando la reflexión.

Es totalmente posible tener la misma funcionalidad de una manera segura para el tipo, simplemente requiriendo que el usuario anule un método para copiar campos en un objeto de datarow, pero eso daría como resultado un gran número de código repetitivo e hinchazón.

¡Trozos!

Rhino Mocks es un marco de burla. Pasas un tipo de interfaz a un método, y detrás de la escena el framework construirá dinámicamente y creará una instancia de un objeto simulado que implemente la interfaz.

Claro, un programador podría escribir a mano el código repetitivo para el objeto simulado, pero ¿por qué querría ella si el marco lo haría por ella?

Metadata!

Podemos decorar métodos con atributos (metadatos), que pueden servir para una variedad de propósitos:

[FilePermission(Context.AllAccess)] // writes things to a file [Logging(LogMethod.None)] // logger doesn''t log this method [MethodAccessSecurity(Role="Admin")] // user must be in "Admin" group to invoke method [Validation(ValidationType.NotNull, "reportName")] // throws exception if reportName is null public void RunDailyReports(string reportName) { ... }

Debe reflexionar sobre el método para inspeccionar los atributos. La mayoría de los marcos AOP para .NET usan atributos para la inyección de políticas.

Claro, puedes escribir el mismo tipo de código en línea, pero este estilo es más declarativo.

¡Hagamos un marco de dependencia!

Muchos contenedores de IoC requieren un cierto grado de reflexión para funcionar correctamente. Por ejemplo:

public class FileValidator { public FileValidator(ILogger logger) { ... } } // client code var validator = IoC.Resolve<FileValidator>();

Nuestro contenedor IoC instanciará un validador de archivos y pasará una implementación apropiada de ILogger al constructor. ¿Qué implementación? Eso depende de cómo se implemente.

Digamos que di el nombre del ensamblado y la clase en un archivo de configuración. El lenguaje debe leer el nombre de la clase como una cadena y usar el reflejo para crear una instancia.

A menos que sepamos la implementación en tiempo de compilación, no existe una forma segura de tipo para instanciar una clase en función de su nombre.

Encuadernación tardía / escritura de pato

Hay todo tipo de razones por las que le gustaría leer las propiedades de un objeto en tiempo de ejecución. Elegiría el registro como el caso de uso más simple: digamos que estaba escribiendo un registrador que acepta cualquier objeto y escupe todas sus propiedades en un archivo.

public static void Log(string msg, object state) { ... }

Puede anular el método de registro para todos los tipos estáticos posibles, o puede usar el reflejo para leer las propiedades en su lugar.

Algunos lenguajes, como OCaml y Scala, son compatibles con el tipado de patos comprobado de forma estática (denominado tipado estructural ), pero a veces simplemente no se tiene conocimiento en tiempo de compilación de una interfaz de objetos.

O como saben los programadores de Java, a veces el sistema de tipos se saldrá con la suya y requieren que escriba todo tipo de código repetitivo. Hay un artículo bien conocido que describe cuántos patrones de diseño se simplifican con el tipado dinámico .

De vez en cuando eludir el sistema de tipo le permite refactorizar su código mucho más allá de lo que es posible con tipos estáticos, lo que resulta en un código un poco más limpio (preferiblemente escondido detrás de una API amigable con el programador :)). Muchos lenguajes estáticos modernos están adoptando la regla de oro "tipado estático siempre que sea posible, tipado dinámico cuando sea necesario", lo que permite a los usuarios cambiar entre código estático y dinámico.


Los complementos son un gran ejemplo.

Las herramientas son otro ejemplo: herramientas de inspección, herramientas de compilación, etc.


Paul Graham tiene un gran ensayo que puede decir lo mejor:

Programas que escriben programas? ¿Cuándo querrías hacer eso alguna vez? No muy a menudo, si piensas en Cobol. Todo el tiempo, si piensas en Lisp. Sería conveniente aquí si pudiera dar un ejemplo de una poderosa macro, ¡y decir allí! ¿Qué hay sobre eso? Pero si lo hiciera, parecería un galimatías para alguien que no conocía a Lisp; no hay espacio aquí para explicar todo lo que necesita saber para entender lo que significa. En Ansi Common Lisp intenté mover las cosas lo más rápido que pude, y aun así no llegué a las macros hasta la página 160.

concluyendo con. . .

Durante los años que trabajamos en Viaweb, leí muchas descripciones de trabajo. Un nuevo competidor parecía surgir de la carpintería cada mes más o menos. Lo primero que haría, después de verificar si tenían una demostración en línea en vivo, era mirar sus listados de trabajo. Después de un par de años de esto, pude decir de qué compañías preocuparse y cuáles no. Cuanto más sabor de TI tenían las descripciones de trabajo, menos peligrosa era la empresa. El tipo más seguro era el que quería la experiencia de Oracle. Usted nunca tuvo que preocuparse por eso. También estabas seguro si decían que querían desarrolladores de C ++ o Java. Si quisieran programadores de Perl o Python, eso sería un poco alarmante, eso está comenzando a parecerse a una compañía en la que el lado técnico, al menos, está dirigido por hackers reales. Si alguna vez hubiera visto un puesto de trabajo buscando hackers Lisp, hubiera estado realmente preocupado.


Proyectos como hibernate (O / R mapping) y StructureMap (inyección de dependencia) serían imposibles sin Reflection. ¿Cómo podría uno resolver estos con polimorfismo solo?

Lo que hace que estos problemas sean tan difíciles de resolver de otra forma es que las bibliotecas no saben directamente nada acerca de la jerarquía de su clase; no pueden. Y, sin embargo, necesitan conocer la estructura de sus clases para, por ejemplo, asignar una fila arbitraria de datos de una base de datos a una propiedad de su clase utilizando solo el nombre del campo y el nombre de su propiedad.

La reflexión es particularmente útil para los problemas de mapeo . La idea de convención sobre el código es cada vez más popular y se necesita algún tipo de Reflexión para hacerlo.

En .NET 3.5+ tienes una alternativa, que es usar árboles de expresiones. Estos están fuertemente tipados, y muchos problemas que se resolvieron clásicamente usando Reflection se han reimplementado usando lambdas y árboles de expresión (ver Ninject NHibernate , Ninject ). Pero tenga en cuenta que no todos los idiomas admiten este tipo de construcciones; cuando no están disponibles, básicamente estás atrapado con Reflection.

En cierto modo (y espero que no estoy despeinando muchas plumas con esto), Reflection se usa muy a menudo como una solución alternativa / pirateo en los lenguajes orientados a objetos para las características que vienen gratis en los lenguajes funcionales. A medida que los lenguajes funcionales se vuelven más populares y / o más idiomas de OO comienzan a implementar más características funcionales (como C #), lo más probable es que empecemos a ver que Reflection se usa cada vez menos. Pero sospecho que siempre seguirá existiendo, para aplicaciones más convencionales como complementos (como uno de los otros respondedores señaló amablemente).


Se trata de un desarrollo rápido.

var myObject = // Something with quite a few properties. var props = new Dictionary<string, object>(); foreach (var prop in myObject.GetType().GetProperties()) { props.Add(prop.Name, prop.GetValue(myObject, null); }


Sin reflexión a menudo tienes que repetirte mucho.

Considere estos escenarios:

  • Ejecute un conjunto de métodos, por ejemplo, los métodos testXXX () en un caso de prueba
  • Genera una lista de propiedades en un generador de interfaz gráfica de usuario
  • Haz que tus clases sean guionizables
  • Implementar un esquema de serialización

Normalmente no puede hacer estas cosas en C / C ++ sin repetir toda la lista de métodos y propiedades afectados en otro lugar del código.

De hecho, los programadores de C / C ++ a menudo usan un lenguaje de descripción de interfaz para exponer interfaces en tiempo de ejecución (proporcionando una forma de reflexión).

El uso juicioso de la reflexión y las anotaciones combinadas con convenciones de codificación bien definidas puede evitar la repetición desenfrenada del código y aumentar la capacidad de mantenimiento.


Sin reflexión, ¡ninguna arquitectura de complementos funcionará!


Un uso aún no mencionado: mientras que la reflexión generalmente se considera "lenta", es posible usar Reflection para mejorar la eficiencia del código que usa interfaces como IEquatable<T> cuando existen, y utiliza otros medios para verificar la igualdad cuando lo hacen no. En ausencia de reflexión, el código que quería probar si dos objetos eran iguales tendría que usar Object.Equals(Object) o verificar en tiempo de ejecución si un objeto implementó IEquatable<T> y, si es así, emitir el objeto a esa interfaz. En cualquier caso, si el tipo de cosa que se compara es un tipo de valor, se requerirá al menos una operación de boxeo. El uso de Reflection hace posible tener una clase EqualityComparer<T> construye automáticamente una implementación específica de IEqualityComparer<T> de IEqualityComparer<T> para cualquier tipo particular T , con esa implementación usando IEquatable<T> si está definido, o usando Object.Equals(Object) si no lo es. La primera vez que se utiliza EqualityComparer<T>.Default para cualquier tipo particular T , el sistema tendrá que pasar por más trabajo del que se necesitaría para probar, una vez, si un tipo particular implementa IEquatable<T> . Por otro lado, una vez que se realiza ese trabajo, no se requerirá más verificación de tipo de tiempo de ejecución, ya que el sistema habrá producido una implementación personalizada de EqualityComparer<T> para el tipo en cuestión.