Pregunta

Estoy trabajando en una pieza de código en C# que trata con los controles de Formularios Windows forms.Aquí un pequeño ejemplo, un pequeño contenedor para obtener el rectángulo delimitador (en coordenadas de pantalla) para algún tipo de control:

public class GUIObject {
    protected Control m_control;

    // [..]

    public virtual Rectangle Bounds {
        get {
            Rectangle r = m_control.Bounds;
            if ( m_control.Parent != null ) {
                return m_control.Parent.RectangleToScreen( r );
            }
            return r;
        }
    }
}

Este código se compila en una biblioteca que es distribuido como un "plugin" para ser cargado en los clientes de las aplicaciones.Sin embargo, resultó que algunos de los clientes que utilizan una versión diferente de Windows Forms en su aplicación de lo que mi plugin fue vinculado en contra.Mi plan era hacer frente a este, haciendo que el código de arriba enlazado en tiempo de ejecución, por lo que se va a trabajar con cualquiera de los Formularios Windows forms versión cargada en el dominio de aplicación actual.Con .NET 4, podría usar el dynamic palabras clave pero por desgracia, este código debe trabajar .NET3 de las aplicaciones.Por lo tanto, comencé a utilizar el reflejo de la API, la introducción de un pequeño ayudante de objeto que hace que el uso de la reflexión de la API un poco mejor:

public class LateBoundObject {
    private Object m_o;

    // [..]

    public Object GetProperty( String name ) {
        PropertyInfo pi = m_o.GetType().GetProperty( name );
        return pi == null ? null
                          : pi.GetValue( m_o, null );
    }

    public Object InvokeMethod( String name, Object[] args ) {
        MethodInfo mi = m_o.GetType().GetMethod( name );
        return mi == null ? null
                          : mi.Invoke( m_o, args );
    }
}

public class GUIObject {
    protected LateBoundObject m_control;

    // [..]

    public virtual Rectangle Bounds {
        get {
            Object r = m_control.GetProperty( "Bounds" );
            if ( r == null) {
                return new Rectangle();
            }

            Object parent = m_control.GetProperty( "Parent" );
            if ( parent != null ) {
                LateBoundObject po = new LateBoundObject( parent );
                r = po.InvokeMethod( "RectangleToScreen",
                                     new Object[] { r } );
            }
            return (Rectangle)r;
        }
    }
}

No muy bonito.Un montón de fundición necesaria sobre la persona que llama lado, y sospecho que tengo que lidiar con la sobrecarga de métodos o propiedades más pronto o más tarde - es un viaje lleno de baches por delante.Idealmente, el objeto contenedor permitiría mantener el código original del mismo.

Por tanto, antes de empezar a arreglar el LateBoundObject clase contenedora hasta me pregunto:¿alguien más tiene experiencia con la fabricación de código de C# enlazado en tiempo de ejecución mediante la reflexión de la API?Si es así, ¿cómo te acercas a ella para evitar el dolor de utilizar el crudo reflejo de la API a un mínimo - no utilizar una clase de contenedor a lo largo de las líneas de LateBoundObject o ¿ir a una ruta completamente diferente?Estoy buscando la menos invasiva de forma tan lejos como el código original se refiere.

¿Fue útil?

Solución

Una idea podría ser crear interfaces para lo que desea que los objetos parecen, a continuación, el uso del Sistema.Reflexión.Emiten para generar las clases que pueden coerece la instancia real.Usted puede hacer esto envolviendo en un generadas dinámicamente objeto de que los apoderados de las llamadas desde su interfaz métodos para la instancia real está envolviendo.

El uso sería algo como esto:

interface IGUIObject 
{
  Rectangle Bounds { get; }
  Rectangle RectangleToScreen(Rectangle bounds);
  IGUIObject Parent { get; }
}

var obj = GetInstance();
var proxy = Reflection.Coerce<IGUIObject>(obj);
return proxy.Parent.RectangleToScreen(proxy.Bounds);

Tengo un blog aquí con un simple punto de partida para hacer dinámica la coacción, incluyendo la aplicación de ejemplo: coaccionar a los tipos y descarga de asambleas

Lo interesante es que con esta técnica se puede conseguir realmente deshacerse de el por llamar a la reflexión, que es muy caro el rendimiento sabio.En lugar de hacer la reflexión una vez en el generador de proxy y lo que genera en realidad llama a la correspondiente propiedad/método/campo directamente después de eso.También con este truco de la dinámica de la asamblea que se genera será descargado al colocar la referencia a la proxy de la instancia.Usted puede tipo de caché de tipo generado para posteriormente realizar proxy creaciones muy rápido.

Su situación es más compleja de lo que a mi de pequeña muestra, pero creo que se podría llegar muy lejos con esto como punto de partida.

Otros consejos

No lo entiendo.Me pase .NET 4 Controles de dll compilado en contra .NET 2 y funcionan bien.

El uso auxiliar de extensiones para la reflexión:

 var r = m_control._P<Rectangle>("Bounds") ?? new Rectangle();
 var parent = m_control._P<Control>("Parent");
 if (parent != null)
   r = parent._M<Rectangle>("RectangleToScreen", r);



static public class ReflectionHlp2
{
  public static T _P<T>(this object item, string name)
  {
    if (item == null)
      return default(T);
    var type = item.GetType();

    var members = type.GetMembers(BindingFlags.GetField | BindingFlags.GetProperty | BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance)
      .Where(_member => _member.Name == name)
      .ToArray();
    if (members.Length == 0)
      return default(T);
    if (members.Length > 1)
      throw new Exception(string.Format("У объекта полей/свойств с именем '{0}' больше чем один: '{1}'", name, members.Length));
    var member = members.First();
    object result;
    if (member is FieldInfo)
      result = ((FieldInfo)member).GetValue(item);
    else
      result = ((PropertyInfo)member).GetValue(item, null);
    if (result is T)
      return (T)result;
    return default(T);
  }
  public static void _P<T>(this object item, string name, T value)
  {
    if (item == null)
      return;
    var type = item.GetType();

    var members = type.GetMembers(BindingFlags.GetField | BindingFlags.GetProperty | BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance)
      .Where(_member => _member.Name == name)
      .ToArray();
    if (members.Length == 0)
      return;
    if (members.Length > 1)
      throw new Exception(string.Format("У объекта полей/свойств с именем '{0}' больше чем один: '{1}'", name, members.Length));
    var member = members.First();
    if (member is FieldInfo)
      ((FieldInfo)member).SetValue(item, value);
    else
      ((PropertyInfo)member).SetValue(item, value, null);
  }
  public static void _M(this object item, string name, params object[] args)
  {
    _M<object>(item, name, args);
  }
  public static T _M<T>(this object item, string name, params object[] args)
  {
    if (item == null)
      return default(T);
    var type = item.GetType();

    var methods = type.GetMethods(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance)
      .Where(_member => _member.Name == name)
      .ToArray();
    if (methods.Length == 0)
      return default(T);
    if (methods.Length > 1)
      throw new Exception(string.Format("Вызов перегруженных методов не поддерживается, у объекта методов с именем '{0}' больше чем один: '{1}'.", name, methods.Length));
    var method = methods.First();
    var result = method.Invoke(item, args);
    if (result is T)
      return (T)result;
    return default(T);
  }
}
Licenciado bajo: CC-BY-SA con atribución
No afiliado a StackOverflow
scroll top