【问题标题】:C# 4.0: casting dynamic to staticC# 4.0:将动态转换为静态
【发布时间】:2011-02-16 15:13:13
【问题描述】:

这是一个分支问题,与我问过的另一个问题 here 有关。我把它分开了,因为它真的是一个子问题:

我在将 dynamic 类型的对象转换为另一个(已知)静态类型时遇到了困难。

我有一个执行此操作的 ironPython 脚本:

import clr
clr.AddReference("System")
from System import *

def GetBclUri():
    return Uri("http://google.com")

请注意,它只是新建一个 BCL System.Uri 类型并返回它。所以我知道返回对象的静态类型

现在在 C# 领域,我正在更新托管内容的脚本并调用此 getter 以返回 Uri 对象:

dynamic uri = scriptEngine.GetBclUri();
System.Uri u = uri as System.Uri; // casts the dynamic to static fine

工作没问题。我现在可以使用强类型 Uri 对象,就好像它最初是静态实例化的一样。

但是....

现在我想定义我自己的 C# 类,该类将在动态领域中更新,就像我对 Uri 所做的那样。我的简单 C# 类:

namespace Entity
{
    public class TestPy // stupid simple test class of my own
    {
        public string DoSomething(string something)
        {
            return something;
        }
    }
}

现在在 Python 中,新建一个这种类型的对象并返回它:

sys.path.append(r'C:..path here...')
clr.AddReferenceToFile("entity.dll")
import Entity.TestPy

def GetTest():
    return Entity.TestPy(); // the C# class

然后在 C# 中调用 getter:

dynamic test = scriptEngine.GetTest();
Entity.TestPy t = test  as Entity.TestPy; // t==null!!!

在这里,演员表不起作用。请注意,“测试”对象(动态)是有效的——我可以调用 DoSomething()——它不会转换为已知的静态类型

string s = test.DoSomething("asdf"); // dynamic object works fine

所以我很困惑。 BCL 类型 System.Uri 将从动态类型转换为正确的静态类型,但我自己的类型不会。显然,我对此没有什么了解...

--

更新:我进行了一系列测试以确保我的程序集引用都正确排列。我更改了引用的程序集版本号,然后查看了 C# 中的 dynamic objects GetType() 信息——它是正确的版本号,但它仍然不会转换回已知的静态类型。

然后我在我的控制台应用程序中创建了另一个类来检查我是否会得到相同的结果,结果是肯定的:我可以在 C# 中获得一个 dynamic 引用到我的 Python 脚本中实例化的静态类型,但是它不会正确转换回已知的静态类型。

--

更多信息:

Anton 在下面建议 AppDomain 程序集绑定上下文可能是罪魁祸首。在做了一些测试后,我认为它很可能是。 . .但我不知道如何解决它!我不知道程序集绑定上下文,因此感谢 Anton,我对程序集解析和那里出现的细微错误有了更多的了解。

所以我在启动脚本引擎之前通过在 C# 中的事件上放置一个处理程序来观察程序集解析过程。这让我看到了 python 引擎启动和运行时开始解析程序集:

private static Type pType = null; // this will be the python type ref

// prior to script engine starting, start monitoring assembly resolution
AppDomain.CurrentDomain.AssemblyResolve 
            += new ResolveEventHandler(CurrentDomain_AssemblyResolve);

... 并且处理程序将 var pType 设置为 python 正在加载的类型

static void CurrentDomain_AssemblyLoad(object sender, AssemblyLoadEventArgs args)
{

    if (args.LoadedAssembly.FullName == 
        "Entity, Version=1.0.0.1, Culture=neutral, PublicKeyToken=null")
    {
        // when the script engine loads the entity assembly, get a reference
        // to that type so we can use it to cast to later.
        // This Type ref magically carries with it (invisibly as far as I can 
        // tell) the assembly binding context
        pType = args.LoadedAssembly.GetType("Entity.TestPy");
    }
}

因此,虽然 python 使用的类型在 C# 中是相同的,但我认为(正如 Anton 提出的那样)不同的绑定上下文意味着对于运行时,这两种类型(“加载绑定上下文”中的一种) ' 和 'loadfrom 绑定上下文)是不同的——所以你不能转换到另一个。

现在我已经掌握了由 Python 加载的类型(以及它的绑定上下文),你瞧,在 C# 中我可以将动态对象转换为这个静态类型并且它可以工作:

dynamic test = scriptEngine.GetTest();
var pythonBoundContextObject = 
       Convert.ChangeType(test, pType); // pType = python bound

string wow = pythonBoundContextObject .DoSomething("success");

但是,叹息,这并不能完全解决问题,因为 var pythonBoundContextObject 虽然类型正确,但仍然带有错误的程序集绑定上下文的污点。这意味着我不能将它传递给我的代码的其他部分,因为我们仍然有这种 bizzare 类型不匹配,其中不可见的绑定上下文幽灵让我感到寒冷。

// class that takes type TestPy in the ctor... 
public class Foo
{
    TestPy tp;

    public Foo(TestPy t)
    {
        this.tp = t;
    }
}

// can't pass the pythonBoundContextObject (from above): wrong binding context
Foo f = new Foo(pythonBoundContextObject); // all aboard the fail boat

因此,解决方案必须在 Python 方面:让脚本加载到正确的程序集绑定上下文中。

在 Python 中,如果我这样做:

# in my python script
AppDomain.CurrentDomain.Load(
    "Entity, Version=1.0.0.1, Culture=neutral, PublicKeyToken=null");

运行时无法解析我的类型:

import Entity.TestPy #fails

【问题讨论】:

  • 注意[AssemblyVersion]
  • 看起来好像 Ironpython 和 C# 使用了不同 DLL 版本的附属程序集。
  • dynamic 对象的类型正确(通过 GetType() 确认)。我什至更改了测试程序集的程序集版本号并重新运行它。再次查看dynamicobjects 类型信息,它显示了新的程序集版本号——但它仍然不会正确转换
  • 这可能是个愚蠢的问题,但是在向下转换为所需类型之前,您是否尝试向上转换为 object
  • 史蒂文:没用。我认为 Anton 是正确的——问题可能是程序集被加载到 Python 中的“既不”上下文中,这与加载到 C# 中的上下文不同(它将在load context 中)。见Suzanne Cook's explanation。因此,由于上下文的差异,类型不会对齐,因此演员表不起作用。

标签: c# dynamic ironpython dynamic-language-runtime


【解决方案1】:

【讨论】:

  • +1。非常感谢!做一个 engine.Runtime.LoadAssembly(typeof(WidgetEntities.Widget).Assembly); 作品!我对解决方案的唯一问题是概括使用(即我希望能够拥有一个可重用的托管组件)意味着我必须了解 IronPython 在 C# 中可以使用的所有可能引用,这基本上意味着我必须加载一切。糟糕。
【解决方案2】:

我敢打赌 IronPython 会将您的 entity.dll 加载到不同的 assembly load context 中,因此您加载了它的两个副本,并且它们中的类型当然是不同的。您可以通过挂钩 AppDomain.AssemblyReslove/AppDomain.AssemblyLoad 并在 IronPython 尝试加载它时返回本地程序集 (typeof (Entity.TestPy).Assembly) 来解决此问题,但我不保证这会起作用。

System.Uri 不会遇到这种情况,因为 mscorlib.dll(可能还有其他一些系统程序集)会被运行时特别处理。

更新: IronPython FAQ 声明如果程序集尚未加载,clr.AddReferenceToFile 使用 Assembly.LoadFile,它会加载到“Neither”上下文中。在调用 IronPython 将程序集加载到默认的 Load 上下文之前,尝试从 Entity.TestPy 访问一个方法。

【讨论】:

  • (1) 在更新 scriptEngine 之前,我在 C# 中实例化了 Entiteis.TestPy 类...运气不好 (2)AssemblyResolve 事件只针对“系统”触发...它从不触发'实体'。我认为你在正确的轨道上,但是......
  • 尝试在 python 中执行 Assembly.Load(...FQ 程序集名称...)。该行将执行,但是当我尝试导入 Entities.Test 时找不到它。 Python 似乎没有意识到负载绑定上下文
  • 对不起,我的意思是挂钩AppDomain.AssemblyLoad
猜你喜欢
  • 2013-05-14
  • 2017-09-22
  • 1970-01-01
  • 1970-01-01
  • 2013-06-24
  • 1970-01-01
  • 2011-02-04
  • 2011-01-24
  • 2013-11-29
相关资源
最近更新 更多