2012-02-29 24 views

Respuesta

39

En el pasado con .NET, ha habido tensión entre el deseo de poder automatizar ciertas funciones a través de la reflexión, y ser capaz de personalizarlos. Por ejemplo, tome el panel de Propiedades en Visual Studio: en escenarios donde se muestra algún tipo de .NET (por ejemplo, un control en una superficie de diseño), puede descubrir y mostrar automáticamente cada propiedad pública que el tipo defina.

El uso de la reflexión de este tipo de comportamiento tipo accionado es útil porque significa que cada propiedad se mostrará sin que el desarrollador del control necesidad de hacer nada. Pero presenta un problema: ¿y si quieres personalizar cosas, p. definición de categorización o una IU de edición personalizada para una propiedad en particular?

La solución clásica en .NET es incluir un montón de atributos personalizados en los miembros relevantes. Sin embargo, un problema es que puede significar que las partes de tu código que hacen un trabajo significativo en tiempo de ejecución terminan dependiendo de las clases que solo hacen algo en el tiempo de diseño; confiar en los atributos evita que sepas separar el tiempo de ejecución y el tiempo de diseño . ¿Realmente desea enviar el código de una interfaz de usuario de diseñador personalizada para el panel de propiedades de VS como parte de una biblioteca de control que terminará en máquinas de usuario final?

Otro problema es que en algunas situaciones es posible que desee para decidir de forma dinámica lo que 'propiedades' que presente. Uno de los ejemplos más antiguos de esto (que se remonta a .NET 1.0) fue poner un DataSet en algún tipo de control de cuadrícula (ya sea del lado del cliente o web). Con un conjunto de datos fuertemente tipado, la reflexión podría ser una forma adecuada para que la cuadrícula descubra qué propiedades proporciona la fuente, pero DataSet también podría usarse dinámicamente, por lo que necesita una forma para que la grilla de datos pregunte en tiempo de ejecución qué columnas mostrar.

(Una respuesta a esto es: ¡diseña tu UI correctamente! Generar cuadrículas directamente como esta conduce a experiencias de usuario horribles. Sin embargo, mucha gente quiere hacerlo de forma perezosa, ya sea una buena idea o no .. .)

por lo que a continuación tiene una situación en la que a veces uno quiere comportamiento de reflexión impulsada, pero a veces usted quiere ser capaz de tomar el control total en tiempo de ejecución.

Aparecieron varias soluciones ad hoc para esto. Tiene toda la familia de tipos TypeDescriptor y PropertyDescriptor, que proporcionan una especie de vista virtualizable sobre la reflexión. De forma predeterminada, esto pasaría todo directamente de la reflexión, pero los tipos tienen la oportunidad de optar por proporcionar descriptores personalizados en tiempo de ejecución, lo que les permite modificar o incluso reemplazar completamente su apariencia. ICustomTypeDescriptor es parte de ese mundo.

que proporciona una solución para el problema de querer comportamiento de reflexión impulsado por defecto con la opción de proporcionar un comportamiento de tiempo de ejecución impulsada por si lo desea. Pero no resuelve el problema donde solo quieres hacer esto en el momento del diseño, y no quieres tener que enviar ese código como parte de los redistribuibles en tiempo de ejecución. Hace

Así que unos pocos años, Visual Studio introdujo mecanismos de su propia ad hoc para aumentar la información de tipo en tiempo de diseño.Hay un montón de comportamientos basados ​​en convenciones en los que Visual Studio descubrirá automáticamente los componentes de tiempo de diseño que están relacionados con componentes de tiempo de ejecución concretos, lo que le permite personalizar la experiencia de diseño sin necesidad de convertir el código relevante en sus redistribuibles. Blend también usa este mecanismo, aunque con algunos ajustes, lo que permite proporcionar diferentes piezas de diseño para VS y Blend.

Por supuesto, nada de eso es visible a través de las API de reflexión normal - VS y Blend tienen una capa de envoltura que se sienta encima de la reflexión para que todo esto funcione.

Así que ahora tenemos dos capas de virtualización que pueden pasar a través de la reflexión, o pueden aumentar lo que sale de la reflexión ...

Parece que en .NET 4.5, el equipo de CLR decidieron que desde diversos los grupos ya estaban haciendo este tipo de cosas, y otros grupos querían hacer más (el equipo MEF tenía requisitos similares para el comportamiento basado en la reflexión-con-opcional-tiempo de ejecución-aumento), este era exactamente el tipo de cosa que debería ser integrado en el tiempo de ejecución.

El nuevo modelo parece ser este: la clase base ReflectionContext es una API abstracta a través de la cual puede obtener una versión virtualizada de la API de reflexión. Es engañosamente simple, porque una de las ideas principales es que ya no necesita API especializadas como el sistema de descriptores de tipo si su único objetivo es obtener un contenedor virtualizable sobre la reflexión: la reflexión ahora se puede virtualizar de inmediato. Para que pueda escribir este tipo de cosas

public static void ShowAllAttributes(Type t) 
{ 
    foreach (Attribute attr in t.GetCustomAttributes(true)) 
    { 
     Console.WriteLine(attr); 
    } 
} 

Ahora que siempre has sido capaz de escribir esto, pero antes de .NET 4.5, código como este siempre estaría trabajando en contra de la información de tipo 'real', ya que utiliza la reflexión . Pero gracias a los contextos de reflexión, ahora es posible proporcionar esto con un Type virtualizado. Por lo que consideran este tipo muy aburrido:

class NoRealAttributes 
{ 
} 

Si sólo tiene que pasar a mi método typeof(NoRealAttributes)ShowAllAttributes, se imprimirá nada. Pero puedo escribir (un tanto artificial) custom context reflexión:

class MyReflectionContext : CustomReflectionContext 
{ 
    protected override IEnumerable<object> GetCustomAttributes(MemberInfo member, IEnumerable<object> declaredAttributes) 
    { 
     if (member == typeof(NoRealAttributes)) 
     { 
      return new[] { new DefaultMemberAttribute("Foo") }; 
     } 
     else 
     { 
      return base.GetCustomAttributes(member, declaredAttributes); 
     } 
    } 
} 

(Por cierto, creo que la distinción entre CustomReflectionContext y su base, ReflectionContext es que este último define la API para un contexto de reflexión virtualizable, mientras CustomReflectionContext añade algunos ayudantes para que sea más fácil para que usted pueda poner en práctica tal cosa) y ahora puedo usar eso para proporcionar una versión virtualizada de la Type para mi clase:.

var ctx = new MyReflectionContext(); 
Type mapped = ctx.MapType(typeof(NoRealAttributes).GetTypeInfo()); 
ShowAllAttributes(mapped); 

En este código, mapped todavía se refiere a un objeto Type , por lo que cualquier cosa que sepa cómo usar la API de reflexión podrá trabajar con ella, pero ahora informará la presencia de un atributo que no está realmente allí. Por supuesto, Type es abstracto, por lo que siempre tenemos algo derivado de eso, y si llama al mapped.GetType() verá que en realidad es System.Reflection.Context.Custom.CustomType en lugar de System.RuntimeType que normalmente vería. Y ese objeto CustomType pertenece a mi contexto personalizado, por lo que cualquier otro objeto API de reflexión que se admita a través de él (por ejemplo, si escribió mapped.Assembly.GetTypes()) también obtendría objetos personalizados que pasan por mi contexto personalizado, que tendría la oportunidad de modificar cualquier cosa que salga

Así que el código puede navegar a través del sistema de tipos utilizando el objeto personalizado Type.A pesar de que dicho código está utilizando la API de reflexión básica ordinaria, ahora tengo la oportunidad de personalizar cualquier cosa que surja de eso si lo considero apropiado.

Solo obtiene esta vista virtualizada si la solicita. Por ejemplo, MEF en .NET 4.5 busca un atributo personalizado que especifique que debe usar un contexto de reflejo personalizado proporcionado por el usuario, pero de lo contrario recurrirá a la reflexión ordinaria. (Y en el caso de mi método ShowAllAttributes, utiliza cualquier objeto Type que elija para pasar - no sé si está obteniendo un objeto tipo virtualizado o 'real')

Así que en resumen, esto significa ya no necesita contenedores ad hoc alrededor de la API de reflexión si desea información de tipo virtualizada.

+0

Esta es una gran respuesta, muchas gracias. Realmente esperaba que permitieran virtualizar cualquier llamada de reflexión existente (lo que habría ayudado con muchas limitaciones innecesarias basadas en la reflexión en bibliotecas MS y no MS existentes). Entonces esto es solo una ayuda incorporada para lo que ya podríamos hacer. Bueno, eso también puede ser útil. –

+0

¿Cuántos puntos puedo otorgar? Por un lado, me hiciste notar que esta cosa oscura con la que tropecé (a través de Reflectora de arrastre) es realmente nueva en 4.5 y no es una de esas cachiporras de interoperabilidad abandonadas que .NET tiene tantas (mencionas algunas). ¡Bonanza! Si puedo virtualizar una propiedad en un tipo sobre la marcha (como se prometió en el documento) y tenerlos visibles en VS intellisense, voy a llorar de alegría. No tengo en cuenta todo el tiempo que perdí en los 1.001 intentos fallidos (el último y quizás el peor fue tratar de subclasificar por completo la constelación 'System.Type'). Woo-hoo fuera para probar esto ... –