Какой наименее инвазивный способ сделать код C # поздним в .NET <4? - PullRequest
2 голосов
/ 12 марта 2012

Я работаю над фрагментом кода C #, который касается элементов управления Windows Forms. Вот небольшой пример, крошечная оболочка для получения ограничивающего прямоугольника (в экранных координатах) для некоторого элемента управления:

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

Этот код скомпилирован в библиотеку, которая распространяется как «плагин» для загрузки в приложения клиентов. Однако оказалось, что некоторые клиенты использовали в своих приложениях версию Windows Forms, отличную от той, с которой был связан мой плагин. Мой план состоял в том, чтобы решить эту проблему, сделав вышеуказанный код с поздней привязкой, чтобы он работал с любой версией Windows Forms, загруженной в текущий домен приложения. В .NET 4 я мог бы использовать ключевое слово dynamic, но, увы, этот код должен работать и с приложениями .NET3. Поэтому я начал использовать API отражения, представив небольшой вспомогательный объект, который делает использование API отражения немного приятнее:

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

Не очень красиво. На стороне вызывающей стороны требовалось много приведения, и я подозреваю, что рано или поздно мне придется иметь дело с перегруженными методами или свойствами - довольно ухабистая поездка впереди. В идеале объект-обертка позволит сохранить исходный код почти таким же.

Итак, прежде чем я начну исправлять класс-оболочку LateBoundObject, я задаюсь вопросом: есть ли у кого-нибудь еще опыт создания кода C # с поздним связыванием с использованием API отражения? Если да, то как вы подходили к этому, чтобы свести к минимуму боль при использовании API-интерфейсов рефлексии - вы также использовали класс-оболочку в соответствии с LateBoundObject или вы пошли совершенно другим путем? Я ищу наименее инвазивный способ с точки зрения оригинального кода.

Ответы [ 3 ]

1 голос
/ 12 марта 2012

Используйте вспомогательные расширения для отражения:

 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);
  }
}
1 голос
/ 12 марта 2012

Одной из идей было бы создать интерфейсы для того, как вы хотите, чтобы объекты выглядели, а затем использовать System.Reflection.Emit для генерации классов, которые могут объединять фактический экземпляр.Вы можете сделать это, поместив его в динамически сгенерированный объект, который передает вызовы от методов интерфейса к фактическому экземпляру, к которому он относится.

Использование будет выглядеть примерно так:

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

У меня естьпост в блоге с простой отправной точкой для того, как выполнить динамическое приведение, включая пример приложения: типы принуждения и выгрузка сборок

Интересно то, что с помощью этой техники вы действительно можете избавиться ототражение за вызов, что очень дорого с точки зрения производительности.Вместо этого вы делаете отражение один раз в генераторе прокси, и то, что вы генерируете, на самом деле вызывает соответствующее свойство / метод / поле непосредственно после этого.Также с помощью этого трюка динамическая сборка, которая генерируется, будет выгружена при удалении ссылки на экземпляр прокси.Вы можете кэшировать тип сгенерированного типа, чтобы сделать последующие прокси-серверы очень быстрыми.

Ваша ситуация сложнее, чем мой маленький пример, но я думаю, что вы могли бы очень далеко зайти с ним в качестве отправной точки.

1 голос
/ 12 марта 2012

Я не понимаю.Я передаю .NET 4 Controls библиотекам, скомпилированным для .NET 2, и они работают просто отлично.

...