2

私は、Windowsフォームコントロールを処理するC#コードの一部に取り組んでいます。これは小さな例です。いくつかのコントロールの境界矩形(画面座標)を取得するための小さなラッパーです。

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フォームをアプリケーションで使用していることが判明しました。私の計画は、上記のコードをレイトバウンドにすることでこれに取り組むことでした。これにより、現在のアプリケーションドメインにロードされているWindowsフォームのバージョンに関係なく機能するようになります。.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ラッパークラスの修正を開始する前に、リフレクションAPIを使用してC#コードをレイトバウンドにした経験がある人はいますか?もしそうなら、生のリフレクションAPIを使用する苦痛を最小限に抑えるために、どのようにアプローチしましたか?また、のラインに沿ってラッパークラスを使用しましたか、LateBoundObjectそれともまったく異なるルートを使用しましたか?元のコードに関する限り、最も侵襲性の低い方法を探しています。

4

3 に答える 3

1

理解できません。.NET2に対してコンパイルされたdllに.NET4コントロールを渡しますが、正常に機能します。

于 2012-03-12T16:24:46.363 に答える
1

リフレクションにヘルパー拡張機能を使用します。

 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);
  }
}
于 2012-03-12T17:00:55.497 に答える
1

1つのアイデアは、オブジェクトをどのように見せたいかを示すインターフェイスを作成し、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);

サンプルアプリを含む、動的強制を行う方法の簡単な出発点を含むブログ投稿があります:強制タイプとアンロードアセンブリ

興味深いのは、この手法を使用すると、実際に呼び出しごとの反射を取り除くことができることです。これは、パフォーマンスの面で非常にコストがかかります。代わりに、プロキシジェネレータで一度リフレクションを実行し、生成したものは実際にはその直後に対応するプロパティ/メソッド/フィールドを呼び出します。また、このトリックを使用すると、プロキシインスタンスへの参照を削除すると、生成された動的アセンブリがアンロードされます。タイプ生成タイプをキャッシュして、後続のプロキシ作成を非常に高速にすることができます。

あなたの状況は私の小さなサンプルよりも複雑ですが、出発点として非常に遠くまで行くことができると思います。

于 2012-03-12T18:35:55.623 に答える