在.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窗体版本有关。使用.NET 4,我可以使用dynamic
关键字但是ala,此代码也应该使用.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的痛苦最小 - 您是否也使用了沿着世乡科典礼线的包装类或者您是否完全不同的路线?就原始代码而言,我正在寻找最小的侵入方式。
解决方案
一个想法是为您希望对象看起来像的接口,然后使用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 4控制到对阵.NET 2编译的DLL,它们只需正常工作。
使用辅助延伸反射:
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);
}
}
.