.NET <4に遅く拘束されたC#コードを作成するための最も侵入的な方法は何ですか?
-
12-12-2019 - |
質問
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 Formsバージョンがどのようなものでも機能します。 .NET 4では、dynamic
キーワードを使用できましたが、ALASは.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
ラッパークラスを修正する前に、私が不思議に思う:他の誰かがReflection APIを使用してC#コードを遅くすることで経験がありますか?もしそうなら、Raw Reflection APIを最小限に使用するという痛みを保つためにどのように近づきましたか - LateBoundObject
の行に沿ってラッパークラスを使用するか、または完全に異なるルートに行きましたか。元のコードが関係する限り、侵略的な方法を探しています。
解決
オブジェクトが、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回の反射を行い、それ以降に対応するプロパティ/ method /フィールドを実際に呼び出します。このトリックでは、プロキシインスタンスへの参照をドロップすると、生成される動的アセンブリはアンロードされます。その後のプロキシ作成を非常に速くするためにタイプ生成タイプをキャッシュできます。
あなたの状況は私の小さなサンプルよりも複雑ですが、私はあなたが出発点としてそれを非常に遠くに得ることができると思います。
他のヒント
私はそれを得ません。私は.NET 2に対してコンパイルされたDLLを.NET 4に渡します。
反射のためのヘルパー拡張を使用:
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);
}
}
.