C#4.0:将动态转换为静态

这是一个与我在这里问到的另一个相关的分支问题。 我把它分开是因为它真的是一个子问题:

我很难将类型为dynamic的对象转换为另一个(已知的)静态类型。

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

 import clr clr.AddReference("System") from System import * def GetBclUri(): return Uri("http://google.com") 

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

现在在C#land,我正在新建脚本托管的东西,并调用此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!!! 

在这里,演员表不起作用。 注意’test’对象(动态)是有效的 – 我可以调用DoSomething() – 它只是不会强制转换为已知的静态类型

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

所以我很困惑。 BCL类型System.Uri将从动态类型转换为正确的静态类型,但我自己的类型不会。 显然我没有得到这个……

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

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

更多信息:

Anton在下面建议AppDomain程序集绑定上下文可能是罪魁祸首。 做了一些测试后我觉得很有可能。 。 。 但我无法弄清楚如何解决它! 我没有意识到assembly绑定的背景,所以感谢Anton,我已经对assembly解决方案和那里出现的微妙错误进行了更多的教育。

所以我在启动脚本引擎之前通过在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正在加载的Type

 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所提出的),不同的绑定上下文意味着对于运行时,两种类型(’load binding context’中的一种类型和’loadfrom binding context)是不同的 – 所以你不能转向另一个。

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

 dynamic test = scriptEngine.GetTest(); var pythonBoundContextObject = Convert.ChangeType(test, pType); // pType = python bound string wow = pythonBoundContextObject .DoSomething("success"); 

但是,感叹,这并不能完全解决问题,因为var pythonBoundContextObject虽然属于正确的类型, 但仍然带有错误的程序集绑定上下文的污点 。 这意味着我无法将其传递给我的代码的其他部分,因为我们仍然有这种混淆类型不匹配 ,其中绑定上下文的隐形幽灵阻止我冷。

 // 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 

以下是IronPython团队的答案,其中涵盖了同样的问题:

C#/ IronPython与共享C#类库互操作

(摘自http://lists.ironpython.com/pipermail/users-ironpython.com/2010-September/013717.html )

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

您没有使用System.Uri遇到此问题,因为运行时特别处理了mscorlib.dll (可能还有其他一些系统程序集)。

更新: IronPython FAQ指出,如果尚未加载程序集,则clr.AddReferenceToFile使用Assembly.LoadFile ,它将加载到“Neither”上下文中。 尝试从Entity.TestPy访问方法, Entity.TestPy再调用IronPython将程序集Load到默认的Load上下文中。