C# 4.0:将动态转换为静态
这是一个分支问题,与我在此处提出的另一个问题相关。我将其分开,因为它实际上是一个子问题:
我在将 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# 中的动态对象 GetType() 信息——它是正确的版本号,但它仍然不会转换回已知的静态类型。
然后,我在控制台应用程序中创建了另一个类,以检查是否会得到相同的结果,结果是肯定的:我可以在 C# 中获得对 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
虽然类型正确,但仍然带有错误的程序集绑定上下文的污点。这意味着我无法将其传递给代码的其他部分,因为我们仍然存在这种奇怪的类型不匹配,其中绑定上下文的无形幽灵让我感到冷淡。
// 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
This is an offshoot question that's related to another I asked here. I'm splitting it off because it's really a sub-question:
I'm having difficulties casting an object of type dynamic
to another (known) static type.
I have an ironPython script that is doing this:
import clr
clr.AddReference("System")
from System import *
def GetBclUri():
return Uri("http://google.com")
note that it's simply newing up a BCL System.Uri type and returning it. So I know the static type of the returned object.
now over in C# land, I'm newing up the script hosting stuff and calling this getter to return the Uri object:
dynamic uri = scriptEngine.GetBclUri();
System.Uri u = uri as System.Uri; // casts the dynamic to static fine
Works no problem. I now can use the strongly typed Uri object as if it was originally instantiated statically.
however....
Now I want to define my own C# class that will be newed up in dynamic-land just like I did with the Uri. My simple C# class:
namespace Entity
{
public class TestPy // stupid simple test class of my own
{
public string DoSomething(string something)
{
return something;
}
}
}
Now in Python, new up an object of this type and return it:
sys.path.append(r'C:..path here...')
clr.AddReferenceToFile("entity.dll")
import Entity.TestPy
def GetTest():
return Entity.TestPy(); // the C# class
then in C# call the getter:
dynamic test = scriptEngine.GetTest();
Entity.TestPy t = test as Entity.TestPy; // t==null!!!
here, the cast does not work. Note that the 'test' object (dynamic) is valid--I can call the DoSomething()--it just won't cast to the known static type
string s = test.DoSomething("asdf"); // dynamic object works fine
so I'm perplexed. the BCL type System.Uri will cast from a dynamic type to the correct static one, but my own type won't. There's obviously something I'm not getting about this...
--
Update: I did a bunch of tests to make sure my assembly refs are all lining up correctly. I changed the referenced assembly ver number then looked at the dynamic
objects GetType() info in C#--it is the correct version number, but it still will not cast back to the known static type.
I then created another class in my console app to check to see I would get the same result, which turned out positive: I can get a dynamic
reference in C# to a static type instantiated in my Python script, but it will not cast back to the known static type correctly.
--
even more info:
Anton suggests below that the AppDomain assembly binding context is the likely culprit. After doing some tests I think it very likely is. . . but I can't figure out how to resolve it! I was unaware of assembly binding contexts so thanks to Anton I've become more educated on assembly resolution and the subtle bugs that crop up there.
So I watched the assembly resolution process by putting a handler on the event in C# prior to starting the script engine. That allowed me to see the python engine start up and the runtime start to resolve assemblies:
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);
... and the handler sets the var pType to the Type that python is loading:
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");
}
}
So while the type used by python is the same on in C#, I'm thinking (as proposed by Anton) that the different binding contexts mean that to the runtime, the two types (the one in the 'load binding context' and the 'loadfrom binding context) are different--so you can't cast on to the other.
So now that I have hold of the Type (along with it's binding context) loaded by Python, lo and behold in C# I can cast the dynamic object to this static type and it works:
dynamic test = scriptEngine.GetTest();
var pythonBoundContextObject =
Convert.ChangeType(test, pType); // pType = python bound
string wow = pythonBoundContextObject .DoSomething("success");
But, sigh, this doesn't totally fix the problem, because the var pythonBoundContextObject
while of the correct type, still carries the taint of the wrong assembly binding context. This means that I can't pass this to other parts of my code because we still have this bizzare type mismatch where the invisible specter of binding context stops me cold.
// 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
So the resolution is going to have to be on the Python side: getting the script to load in the right assembly binding context.
in Python, if I do this:
# in my python script
AppDomain.CurrentDomain.Load(
"Entity, Version=1.0.0.1, Culture=neutral, PublicKeyToken=null");
the runtime can't resolve my type:
import Entity.TestPy #fails
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(2)
以下是 IronPython 团队的回答,涵盖了相同的问题:
C# / IronPython 与共享 C# 类库的互操作
(摘自 http://lists.ironpython.com/pipermail/users-ironpython.com/2010-September/013717.html)
Here's an answer from the IronPython team which covers the same problem:
C# / IronPython Interop with shared C# Class Library
(Lifted from http://lists.ironpython.com/pipermail/users-ironpython.com/2010-September/013717.html )
我敢打赌 IronPython 会将您的
entity.dll
加载到不同的 程序集加载上下文,这样您就加载了它的两个副本,并且它们中的类型当然不同。您可以通过挂钩AppDomain.AssemblyReslove
/AppDomain.AssemblyLoad
并返回本地程序集 (typeof (Entity.TestPy).Assembly< /code>) 当 IronPython 尝试加载它时,但我不保证这会起作用。
使用
System.Uri
时不会出现这种情况,因为mscorlib.dll
(可能还有一些其他系统程序集)会被运行时特殊对待。更新: IronPython 常见问题解答指出,如果程序集不是'已经加载的
clr.AddReferenceToFile
使用Assembly.LoadFile
,它加载到“Neither”上下文中。在调用 IronPython 将程序集加载到默认Load
上下文之前,尝试从 Entity.TestPy 访问方法。I bet that IronPython loads your
entity.dll
into a different assembly load context, so that you have two copies of it loaded and the types in them are of course different. You might be able to work around this issue by hookingAppDomain.AssemblyReslove
/AppDomain.AssemblyLoad
and returning your local assembly (typeof (Entity.TestPy).Assembly
) when IronPython tries to load it, but I don't guarantee that this will work.You don't experience this with
System.Uri
becausemscorlib.dll
(and maybe some other system assemblies) is treated specially by the runtime.Update: IronPython FAQ states that if the assembly isn't already loaded
clr.AddReferenceToFile
usesAssembly.LoadFile
, which loads into the 'Neither' context. Try accessing a method fromEntity.TestPy
before calling IronPython to load the assembly into the defaultLoad
context.