【问题标题】:What's the least invasive way to make C# code late-bound in .NET < 4?在 .NET < 4 中使 C# 代码后期绑定的侵入性最小的方法是什么?
【发布时间】:2012-03-12 16:00:25
【问题描述】:

我正在编写一段处理 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 的包装类,或者您是否采用了完全不同的路线?就原始代码而言,我正在寻找侵入性最小的方法。

【问题讨论】:

  • 有多少个“WinForms 版本”?我认为你应该只针对 Fx2.0 的库,并为 Fx1 和可能为 Fx4 制作一个单独的库
  • @HenkHolterman:确实如此,对于 Windows 窗体,只需多次构建插件就足够了。但是,我在使用其他工具包(WPF 和许多 3rd 方工具包,如“扩展 WPF”)时也面临同样的问题。所以我想知道是否可以通过让我的代码后期绑定来避免多次构建所有内容。
  • 您确定要让您的 Fx4 用户使用后期绑定的 Fx1.1 代码吗?
  • @HenkHolterman:还有多少负担还有待观察;我肯定会(在时间和空间上)分析生成的代码以检查它的成功率。在此之前,我首先想澄清一下使用反射 API 使现有的 C# 代码库后期绑定是否可行(可能通过使用一些便利的包装器)。

标签: c# .net late-binding


【解决方案1】:

一个想法是为您希望对象的外观创建接口,然后使用 System.Reflection.Emit 生成可以 coerece 实际实例的类。您可以通过将其包装在一个动态生成的对象中来实现这一点,该对象代理从其接口方法调用到它所包装的实际实例。

用法如下所示:

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

我在这里有一篇博文,其中包含如何进行动态强制的简单起点,包括示例应用程序:coercing types and unloading assemblies

有趣的是,通过这种技术,您实际上可以摆脱每次调用反射,这在性能方面非常昂贵。相反,您在代理生成器中进行一次反射,然后您生成的内容实际上直接调用相应的属性/方法/字段。同样通过这个技巧,当您删除对代理实例的引用时,生成的动态程序集将被卸载。您可以缓存类型生成的类型,以使后续的代理创建速度非常快。

您的情况比我的小样本更复杂,但我认为您可以以此为起点走得更远。

【讨论】:

  • 非常有趣!我一定会看看的。我不断听到关于 Emit 设施的好消息,我怀疑它在这里可能会有所帮助。非常感谢您的指点!
【解决方案2】:

我不明白。我将 .NET 4 控件传递给针对 .NET 2 编译的 dll,它们工作得很好。

【讨论】:

  • 问题是我的程序集动态加载到客户应用程序域中,链接到与他使用的不同版本的第 3 方程序集 - 所以我宁愿使用他拥有的版本已经加载而不需要我的插件所针对的任何东西。
  • 在你的情况下,我更喜欢在加载时编译(加载插件时调用 csc.exe 并加载生成的程序集)。
【解决方案3】:

使用辅助扩展进行反射:

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

【讨论】:

    猜你喜欢
    • 2013-07-08
    • 2016-11-20
    • 1970-01-01
    • 2010-10-05
    • 1970-01-01
    • 2010-09-13
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多