Was ist der am wenigsten invasive Weg, um C#-Code in .NET <4 spät gebunden zu machen?

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

  •  12-12-2019
  •  | 
  •  

Frage

Ich arbeite an einem C#-Code, der sich mit Windows Forms-Steuerelementen befasst.Hier ist ein kleines Beispiel, ein kleiner Wrapper zum Abrufen des umgrenzenden Rechtecks ​​(in Bildschirmkoordinaten) zur Steuerung:

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

Dieser Code wird in einer Bibliothek kompiliert, die dann als „Plugin“ verteilt wird, um in Kundenanwendungen geladen zu werden.Es stellte sich jedoch heraus, dass einige Kunden in ihrer Anwendung eine andere Version von Windows Forms verwendeten als die, mit der mein Plugin verlinkt war.Mein Plan bestand darin, dieses Problem zu lösen, indem ich den obigen Code spät eingebunden habe, sodass er mit jeder Windows Forms-Version funktioniert, die in der aktuellen Anwendungsdomäne geladen ist.Mit .NET 4 könnte ich das verwenden dynamic Schlüsselwort, aber leider sollte dieser Code auch mit .NET3-Anwendungen funktionieren.Daher begann ich mit der Verwendung der Reflection-API und führte ein kleines Hilfsobjekt ein, das die Verwendung der Reflection-API etwas angenehmer macht:

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

Nicht sehr hübsch.Auf der Aufruferseite ist viel Casting nötig, und ich vermute, dass ich mich früher oder später auch mit überlasteten Methoden oder Eigenschaften auseinandersetzen muss – eine ziemlich holprige Fahrt.Im Idealfall würde das Wrapper-Objekt es ermöglichen, den Originalcode nahezu unverändert zu lassen.

Also, bevor ich anfange, das zu reparieren LateBoundObject Wrapper-Klasse nach oben Ich frage mich:Hat sonst noch jemand Erfahrung damit, C#-Code mithilfe der Reflection-API spät zu binden?Wenn ja, wie sind Sie vorgegangen, um den Aufwand bei der Verwendung der Raw-Reflection-API auf ein Minimum zu beschränken? Haben Sie auch eine Wrapper-Klasse im Sinne von verwendet? LateBoundObject Oder bist du einen ganz anderen Weg gegangen?Ich suche nach dem am wenigsten invasiven Weg, was den Originalcode betrifft.

War es hilfreich?

Lösung

Eine Idee wäre, Schnittstellen dafür zu erstellen, wie die Objekte aussehen sollen, und dann System.Reflection.Emit zu verwenden, um Klassen zu generieren, die die tatsächliche Instanz erzwingen können.Sie können dies tun, indem Sie es in ein dynamisch generiertes Objekt einbinden, das Aufrufe seiner Schnittstellenmethoden an die tatsächliche Instanz weiterleitet, die es einschließt.

Die Nutzung würde etwa so aussehen:

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

Ich habe hier einen Blog-Beitrag mit einem einfachen Ausgangspunkt für die Durchführung dynamischer Zwänge, einschließlich einer Beispiel-App: Zwangsarten und Entladen von Baugruppen

Interessant ist, dass Sie mit dieser Technik tatsächlich die Reflexion pro Anruf beseitigen können, die in Bezug auf die Leistung sehr teuer ist.Stattdessen führen Sie die Reflektion einmal im Proxy-Generator durch und das, was Sie generieren, ruft direkt danach tatsächlich die entsprechende Eigenschaft/Methode/das entsprechende Feld auf.Auch mit diesem Trick wird die generierte dynamische Assembly entladen, wenn Sie den Verweis auf die Proxy-Instanz löschen.Sie können vom Typ generierte Typen zwischenspeichern, um nachfolgende Proxy-Erstellungen sehr schnell zu ermöglichen.

Ihre Situation ist komplexer als meine kleine Stichprobe, aber ich denke, dass Sie damit als Ausgangspunkt sehr weit kommen könnten.

Andere Tipps

Ich verstehe es nicht.Ich übergebe .NET 4-Steuerelemente an DLLs, die mit .NET 2 kompiliert wurden, und sie funktionieren einwandfrei.

Verwenden Sie Hilfserweiterungen zur Reflexion:

 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);
  }
}
Lizenziert unter: CC-BY-SA mit Zuschreibung
Nicht verbunden mit StackOverflow
scroll top