.NET <4에서 c # 코드를 늦게 만들 수있는 가장 중요한 방법은 무엇입니까?
-
12-12-2019 - |
문제
Windows Forms 컨트롤을 처리하는 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
키워드를 사용할 수 있지만이 코드는 .NET3 응용 프로그램에서도 작동해야합니다. 따라서 나는 반사 API를 사용하기 시작하여 리플렉션 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
라인을 따라 래퍼 클래스를 사용 했습니까? 아니면 완전히 다른 경로를 사용 했습니까? 원래 코드가 염려되는 한 최소 침습적 인 방법을 찾고 있습니다.
해결책
하나의 아이디어는 오브젝트가 객체를 원하는 것과 같은 인터페이스를 작성하는 것 다음으로 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);
.
샘플 앱을 포함하여 동적 강압을 수행하는 방법을위한 간단한 출발점이있는 블로그 게시물이 있습니다. 강요 유형 및 어셈블리 언로드
흥미로운 점은이 기술을 사용하여 실제로 통화당 성능이 현명한 통화 별 반사를 제거 할 수 있다는 것입니다. 대신 프록시 생성기에서 한 번 반성을 수행하고 생성 된 내용은 실제로 해당 속성 / 메소드 / 필드를 직접 호출합니다. 또한이 속임수를 사용하여 프록시 인스턴스에 대한 참조를 삭제하면 생성 된 동적 어셈블리가 언로드됩니다. 생성 된 유형 생성 유형을 캐시하여 후속 프록시 작성을 매우 빠르게 만들 수 있습니다.
귀하의 상황은 내 작은 샘플보다 복잡하지만 출발점으로서 매우 멀리 떨어져있을 수 있다고 생각합니다.
다른 팁
나는 그것을 얻지 못한다..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);
}
}
.