zoukankan      html  css  js  c++  java
  • [翻译]修改 .NET 对象使其在 IronPython 中表现出动态性(属性注入)

    原文:http://blogs.msdn.com/srivatsn/comments/8383517.aspx

    修改 .NET 对象使其在 IronPython 中表现出动态性


    假设你要和一个 .NET 的库进行互操作,但同时你又想让它表现的像动态语言中的对象那样,你想动态的给对象添加/删除方法或属性。在 python 中你可以这样写:

    class x(object):
        pass
    
    y = x()
    y.z = 42
    dir(y)

    这样 dir(y) 就会包含 z. 如果 x 是 .NET 类而不是一个 python 类呢,能做到这一点吗?让我们尝试一个简单的 .NET 类:

    public class TestExt
    {
    }

    在 IronPython 中你可以这样:

    import clr
    clr.AddReference("TestExtensions.dll")
    from TestExtensions import TestExt
    y = TestExt()
    y.z = 42


    但这个代码会抛出 AttributeError,提示 'TestExt' 不存在属性 'z'。现在该怎么办呢?这正是 DLR 的扩展机制所要做的。有5个方法可以让 .NET 类来实现,这些方法可以对 binder 显示特殊含义,它们是:

    • GetCustomMember – 在常规的 .NET 查找前执行
    • GetBoundMember – 在常规的 .NET 查找后执行
    • SetMember – 在常规的 .NET 成员赋值前执行
    • SetMemberAfter – 在常规的 .NET 成员赋值后执行
    • DeleteMember – 在常规的 .NET 操作符访问之前执行(没有对应的 .NET 版本 —— 因此是唯一的一个)

    .NET 类可以实现这些函数,并用 SpecialName 特性 来标注它们。现在绑定规则可以在常规 .NET 绑定的前后,调用 Getter/Setter. GetCustomMember/SetMember 首先被调用,并且,如果它返回一个值,则会被当作成员查找的返回值。这就可以覆盖任意可能存在的 .NET 成员。但如果你从该函数中返回 OperationFailed.Value,就会按常规方法继续进行查找。GetBoundMember/SetMemberAfter 在常规调用失败的时候会被调用到 —— ‘失败’表示不存在要绑定到的名字的成员。记住这些,我们来修改一下 .NET 类,向其中添加一些东西:

    Dictionary<string, object> dict = new Dictionary<string, object>();
    [SpecialName]
    public object GetBoundMember(string name)
    {
        if (dict.ContainsKey(name))
            return dict[name];
        else
            return OperationFailed.Value;
    }
    
    [SpecialName]
    public void SetMemberAfter(string methodName, object o)
    {
        dict.Add(methodName, o);
    }

    现在如果我尝试 y.z = 42, 代码会成功运行。同样我可以把 y.z 设定为一个函数,这样就可以调用 y.z() 了。换一种方法,我也可以重写 GetCustomMember 和 SetMember 方法来实现,例子一样有效,因为如果在 dict 中查找不到成员就会返回 OperationFailed.Value. 但这会带来一个负担,就是会影响到所有 .NET 成员的查找过程。

    SetMember 函数可以返回 bool 而不是 void. 如果返回 bool 值,这个返回值会控制是否继续进行绑定查找。

    那么,这个特性的作用是什么?我为什么要用它呢?假设我们要写一个类似下面 xml 所表示的对象模型:

    <foo>
        
    <bar>baz</bar>
    </foo>


    我想通过 foo.bar 访问这个 xml,并且取得的值应该是 baz. 要实现这个功能,只要给 .NET 的 XmlElement 类添加 GetBoundMember 方法,该方法去进行查找,并返回一个 XmlElement. 或者,还可以给 XmlElement 加一个扩展方法。但是扩展方法并不出现在反射的结果中,所以在 IronPython 中目前还没有这个支持。好在有一个避开的办法:可以用 ExtensionType 特性去修饰一个 assembly,该特性指出你要去扩展哪个类型,用哪个类来扩展。然后,你需要注册一次这个 assembly,以使这些方法被注入到合适的地方去。以后也许会修改为其他更好的实现方法,但目前而言,这个办法是可用的。下面就是你需要实现的代码:


    [assembly: ExtensionType(typeof(System.Xml.XmlElement), typeof(TestExtensions.ExtClass.XmlElementExtension))]
    namespace TestExtensions
    {    
        public class ExtClass
        {
            static ExtClass()
            {
                Microsoft.Scripting.Runtime.RuntimeHelpers.RegisterAssembly(typeof(ExtClass).Assembly);
            }
    
            public static XmlElement Load(string fileName)
            {
                XmlDocument doc = new XmlDocument();
                doc.Load(fileName);
                return doc.DocumentElement;
            }
            public static class XmlElementExtension
            {
                [SpecialName]
                public static object GetCustomMember(object myObj, string name)
                {
                    XmlElement xml = myObj as XmlElement;
    
                    if (xml != null)
                    {
                        for (XmlNode n = xml.FirstChild; n != null; n = n.NextSibling)
                        {
                            if (n is XmlElement && string.CompareOrdinal(n.Name, name) == 0)
                            {
                                if (n.HasChildNodes && n.FirstChild == n.LastChild && n.FirstChild is XmlText)
                                {
                                    return n.InnerText;
                                }
                                else
                                {
                                    return n;
                                }
    
                            }
                        }
                    }
                    return OperationFailed.Value;
                }
            }
        }
    }
    

    现在,在 IronPython 中可以这样写:

    import clr
    clr.AddReference("TestExtensions.dll")
    from TestExtensions import ExtClass
    foo = ExtClass.Load("test.xml")
    print foo.bar
    结果会输出 "baz".

    Published Saturday, April 12, 2008 3:16 AM by srivatsn
  • 相关阅读:
    【Win 10 应用开发】获取本机的IP地址
    【Win 10应用开发】延迟共享
    【Win 10 应用开发】共享目标(UWP)
    【Win 10应用开发】响应系统回退键的导航事件
    编写Windows服务疑问2:探索服务与安装器的关系
    编写Windows服务疑问1:操作过程
    服务器常见错误代码500、501、502、503、504、505
    git reset与git revert的区别
    Redis集群方案怎么做?
    ThinkPHP设计模式与Trait技术
  • 原文地址:https://www.cnblogs.com/RChen/p/1151899.html
Copyright © 2011-2022 走看看