Quelle est la manière la moins invasive de rendre le code C# lié tardivement dans .NET <4 ?

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

  •  12-12-2019
  •  | 
  •  

Question

Je travaille sur un morceau de code C# qui traite des contrôles Windows Forms.Voici un petit exemple, un petit wrapper permettant d'obtenir le rectangle de délimitation (en coordonnées d'écran) pour un certain contrôle :

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

Ce code est compilé dans une bibliothèque qui est distribuée sous forme de "plugin" à charger dans les applications clients.Cependant, il s'est avéré que certains clients utilisaient dans leur application une version de Windows Forms différente de celle à laquelle mon plugin était lié.Mon plan était de résoudre ce problème en rendant le code ci-dessus lié tardivement, afin qu'il fonctionne avec n'importe quelle version de Windows Forms chargée dans le domaine d'application actuel.Avec .NET 4, je pourrais utiliser le dynamic mot-clé mais hélas, ce code devrait également fonctionner avec les applications .NET3.Par conséquent, j'ai commencé à utiliser l'API de réflexion, en introduisant un petit objet d'assistance qui rend l'utilisation de l'API de réflexion un peu plus agréable :

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

Pas très joli.Beaucoup de casting est nécessaire du côté de l'appelant, et je soupçonne que je devrai également faire face à des méthodes ou des propriétés surchargées tôt ou tard - un parcours assez cahoteux à venir.Idéalement, l'objet wrapper permettrait de conserver le code d'origine à peu près le même.

Donc, avant de commencer à réparer le LateBoundObject cours de wrapper up, je me demande :Quelqu'un d'autre a-t-il de l'expérience dans la création de code C# à liaison tardive à l'aide de l'API de réflexion ?Si tel est le cas, comment l'avez-vous abordé pour réduire au minimum la difficulté d'utiliser l'API de réflexion brute - avez-vous également utilisé une classe wrapper du type LateBoundObject ou as-tu emprunté un chemin totalement différent ?Je recherche le moyen le moins invasif en ce qui concerne le code original.

Était-ce utile?

La solution

Une idée serait de créer des interfaces correspondant à ce à quoi vous souhaitez que les objets ressemblent, puis d'utiliser System.Reflection.Emit pour générer des classes capables de contraindre l'instance réelle.Vous pouvez le faire en l'encapsulant dans un objet généré dynamiquement qui transmet les appels de ses méthodes d'interface à l'instance réelle qu'il encapsule.

L'utilisation ressemblerait à ceci :

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

J'ai un article de blog ici avec un point de départ simple sur la façon d'effectuer une coercition dynamique, y compris un exemple d'application : types de contrainte et déchargement d'assemblages

Ce qui est intéressant, c'est qu'avec cette technique, vous pouvez réellement vous débarrasser de la réflexion par appel, ce qui est très coûteux en termes de performances.Au lieu de cela, vous effectuez la réflexion une fois dans le générateur de proxy et ce que vous générez appelle en fait la propriété/méthode/champ correspondant directement après cela.De plus, avec cette astuce, l'assembly dynamique généré sera déchargé lorsque vous supprimerez la référence à l'instance proxy.Vous pouvez mettre en cache le type généré pour effectuer des créations de proxy ultérieures très rapides.

Votre situation est plus complexe que mon petit échantillon mais je pense que vous pourriez aller très loin avec cela comme point de départ.

Autres conseils

Je ne comprends pas.Je transmets les contrôles .NET 4 aux DLL compilées avec .NET 2 et ils fonctionnent très bien.

Utilisez des extensions d'assistance pour la réflexion :

 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);
  }
}
Licencié sous: CC-BY-SA avec attribution
Non affilié à StackOverflow
scroll top