O que é menos invasivo maneira de fazer C# código de ligação tardia em .NET < 4?

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

  •  12-12-2019
  •  | 
  •  

Pergunta

Eu estou trabalhando em um pedaço de código em C#, que lida com os controlos de Formulários do Windows.Aqui está um pequeno exemplo, uma pequena wrapper para obter o retângulo delimitador (em coordenadas de tela) para alguns de controle:

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 é compilado em uma biblioteca que é distribuído como um "plugin" para ser carregado para clientes de aplicativos.No entanto, descobriu-se que alguns clientes usada uma versão diferente do Windows Formulários na sua aplicação, que o que meu plugin foi ligado.Meu plano era para enfrentar esta fazendo o código acima tardia, de modo que ele vai trabalhar com qualquer versão de Windows Forms é carregado no domínio de aplicativo atual.Com .NET 4, eu poderia usar a dynamic palavra-chave, mas, infelizmente, este código deve trabalhar .NET3 aplicações bem.Daí, comecei a usar a API de reflexão, apresentando um pouco objecto de programa auxiliar que torna o uso da API de reflexão um pouco melhor:

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

Não muito bonita.Um monte de fundição necessário no lado do chamador, e eu suspeito que eu tenho que lidar com métodos sobrecarregados ou propriedades, mais cedo ou mais tarde - muito atribulado em frente.Idealmente, o objeto wrapper permitiria manter o código original, muito mesmo.

Assim, antes de eu começar a reparar os LateBoundObject classe de wrapper até eu me pergunto:alguém mais tem experiência com criação de código C# tardia usando a API de reflexão?Se sim, como você se aproxima para manter a dor de usar o raw reflexão API para um mínimo - você também usar uma classe de wrapper ao longo das linhas de LateBoundObject ou você foi um percurso totalmente diferente?Eu estou procurando o menos invasivo quanto o código original está em causa.

Foi útil?

Solução

Uma ideia seria criar interfaces para o que você deseja que os objetos a aparência, em seguida, usar o Sistema.Reflexão.Emitem para gerar classes que podem coerece a instância real.Você pode fazer isso por envolvê-lo em uma gerado dinamicamente objeto que proxies de chamadas a partir do seu interface de métodos para a instância real é de moldagem.

O uso seria algo parecido com isto:

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

Eu tenho um blog post aqui com um simples ponto de partida para saber como fazer dinâmicas de coacção, incluindo o aplicativo de exemplo: coagir tipos e descarga de montagens

O que é interessante é que, com esta técnica você pode realmente se livrar do per-call-reflexão, que é muito caro desempenho sábio.Em vez de você fazer a reflexão, uma vez que o gerador de proxy e o que você gerar, na verdade, chama o correspondente método/propriedade/campo diretamente depois.Também com esse truque a montagem dinâmica que é gerada será descarregado quando você soltar a referência para o proxy de instância.Você pode armazenar em cache tipo gerado tipo para fazer subsequentes proxy criações muito rápido.

Sua situação é mais complexa do que o meu pequeno exemplo, mas eu acho que você poderia chegar muito longe com ele como um ponto de partida.

Outras dicas

Eu não entendo.Eu passo .NET 4 Controles a dll compilada contra .Rede 2 e eles funcionam bem.

Utilizar gestor de extensões para reflexão:

 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 em: CC-BY-SA com atribuição
Não afiliado a StackOverflow
scroll top