Qual è il modo meno invasivo per creare il codice C # legati in ritardo in .NET <4?

StackOverflow https://stackoverflow.com//questions/9670466

  •  12-12-2019
  •  | 
  •  

Domanda

Sto lavorando su un pezzo di codice C # che si occupa dei controlli dei moduli di Windows. Ecco un piccolo esempio, un minuscolo involucro per ottenere il rettangolo di delimitazione (nelle coordinate dello schermo) per qualche controllo:

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

Questo codice è compilato in una libreria che è distribuita come "plugin" da caricare nelle applicazioni dei clienti. Tuttavia, è risultato che alcuni clienti hanno utilizzato una versione diversa dei moduli Windows nella loro applicazione di quello che il mio plug-in è stato collegato. Il mio piano doveva affrontarlo facendo il codice sopra riportato in ritardo, in modo che funzionerà con qualsiasi versione di Windows Forms sia caricata nel dominio corrente dell'applicazione. Con .NET 4, potrei usare la parola chiave dynamic ma Ahalo, questo codice dovrebbe funzionare anche con applicazioni .Net3. Quindi, ho iniziato a utilizzare l'API di riflessione, introducendo un piccolo oggetto helper che rende l'utilizzo dell'API di riflessione un po 'più bello:

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

Non è molto carina. Un sacco di fusione necessaria sul lato del chiamante, e sospetto di dover affrontare metodi o proprietà sovraccaricati prima o poi, anche un giro accogliente. Idealmente, l'oggetto del wrapper consentirebbe di mantenere il codice originale molto lo stesso.

Allora, prima di iniziare a fissare la classe del wrapper LateBoundObject, mi chiedo: qualcun altro ha esperienza con la creazione di C # del codice in ritardo con l'API di riflessione? Se è così, come ti sei avvicinato a tenere il dolore dell'utilizzo dell'API RAW Reflection a un minimo - hai anche usato una classe wrapper lungo le linee di LateBoundObject o hai fatto un percorso totalmente diverso? Sto cercando il modo meno invasivo per quanto riguarda il codice originale.

È stato utile?

Soluzione

Un'idea sarebbe quella di creare interfacce per ciò che desideri sembrare gli oggetti, quindi utilizzare System.Reflection.emit per generare classi in grado di costringere l'istanza effettiva. Puoi farlo avvolgendolo in un oggetto generato dinamicamente che proxy chiama dai suoi metodi di interfaccia all'istanza effettiva che è involto.

L'utilizzo sembrerebbe qualcosa del genere:

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

Ho un post sul blog qui con un semplice punto di partenza per come fare coercizione dinamica, inclusa l'app di esempio: Tipi di coercizione e gruppi di scarico

Cosa è interessante è che con questa tecnica puoi effettivamente sbarazzarsi della riflessione per chiamata, che è molto costosa prestazioni saggia. Invece fai la riflessione una volta nel generatore proxy e ciò che generi effettivamente chiama il corrispondente proprietà / metodo / campo direttamente dopo di ciò. Anche con questo trucco il gruppo dinamico che viene generato sarà scaricato quando si esegue il riferimento all'istanza del proxy. Puoi cache tipo generato tipo per rendere le successive creazioni proxy molto velocemente.

La tua situazione è più complessa del mio piccolo campione, ma penso che potresti arrivare molto lontano con esso come punto di partenza.

Altri suggerimenti

Non capisco.Passo .NET 4 controlli alle DLL compilata contro .NET 2 e funzionano bene.

Usa estensioni di helper per la riflessione:

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

Autorizzato sotto: CC-BY-SA insieme a attribuzione
Non affiliato a StackOverflow
scroll top