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
上下文中。