使用动态类型作为方法参数时的奇怪行为 [英] Strange behaviour when using dynamic types as method parameters

查看:128
本文介绍了使用动态类型作为方法参数时的奇怪行为的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我有以下接口是现有项目的一部分。我想让使用动态对象调用Store(..)函数成为可能。但我不想改变Interface层次结构(如果可能的话)。

  public interface IActualInterface 
{
void Store(object entity);
}
public interface IExtendedInterface:IActualInterface
{
//接口项不重要
}
public class Test:IExtendedInterface
{
public void Store(object entity)
{
Console.WriteLine(Storing:+ entity.ToString());
}
}

和以下代码:

  IExtendedInterface extendedInterfaceTest = new Test(); 
IActualInterface actualInterfaceTest = new Test();
Test directTest = new Test();

dynamic employee = new ExpandoObject();
employee.Name =John Smith;
employee.Age = 33;
employee.Phones = new ExpandoObject();
employee.Phones.Home =0111 123123;
employee.Phones.Office =027 321123;
employee.Tags = new List< dynamic>(){123.4D,99.54D};

try
{
extendedInterfaceTest .Store(employee);
}
catch(RuntimeBinderException rbEx)
{
Console.WriteLine(rbEx.Message);
}

//转换为(对象)运行良好,因为它没有在运行时解析
extendedInterfaceTest.Store((object)employee);

//这是因为IActualInterface实现'Store'
actualInterfaceTest.Store(employee);
//这也可以正常工作(directTest:IProxyTest)
directTest.Store(employee);

当我调用 extendedInterfaceTest.Store(employee),它引发一个运行时绑定程序异常。为什么当接口类型相同的底层类型时,接口类型会有所不同?我可以调用它 IActualInterface 类型,但不能调用 IExtendedInterface



我理解当调用带有动态参数的函数时,解析会在运行时发生,但为什么会有不同的行为?


<你需要记住的是,动态分辨率基本上和静态分辨率是一样的,但是在运行时。任何不能被CLR解决的东西都不会被DLR解决。



让我们来看看这个小程序,灵感来自于你的,并且不使用动态:

 命名空间ConsoleApplication38 {

public interface IActualInterface {
void Store对象实体);
}
public interface IExtendedInterface:IActualInterface {
}
public class TestInterface:IExtendedInterface {
public void Store(object entity){
}


public abstract class ActualClass {
public abstract void Store(object entity);
}
public abstract class ExtendedClass:ActualClass {
}
public class TestClass:ExtendedClass {
public override void Store(object entity){
}
}

类程序{

static void TestInterfaces(){
IActualInterface actualTest = new TestInterface();
IExtendedInterface extendedTest = new TestInterface();
TestInterface directTest = new TestInterface();

actualTest.Store(null);
extendedTest.Store(null);
directTest.Store(null);
}

static void TestClasses(){
ActualClass actualTest = new TestClass();
ExtendedClass extendedTest = new TestClass();
TestClass directTest = new TestClass();

actualTest.Store(null);
extendedTest.Store(null);
directTest.Store(null);
}

static void Main(string [] args){
TestInterfaces();
TestClasses();
}
}
}

一切都很好。但是编译器真正生成了什么?让我们看看使用ILdasm。



对于界面:

  actualTest.Store 
IL_0015:callvirt实例void ConsoleApplication38.IActualInterface :: Store(object)

// extendedTest.Store
IL_001d:callvirt实例void ConsoleApplication38.IActualInterface :: Store object)

// directTest.Store
IL_0025:callvirt实例void ConsoleApplication38.TestInterface :: Store(object)

我们可以看到,C#编译器总是生成对定义了该方法的接口或类的调用。 IActualInterface 有一个Store的方法槽,所以它用于 actualTest.Store IExtendedInterface 不会,因此 IActualInterface 用于调用。 TestInterface 使用 newslot IL修饰符定义一个新方法Store,有效地为该方法在vtable中分配一个新槽,所以它直接使用,因为 directTest TestInterface



对于类:

  // actualTest.Store 
IL_0015:callvirt instance void ConsoleApplication38.ActualClass :: Store(object)

// extendedTest.Store
IL_001d:callvirt实例void ConsoleApplication38.ActualClass :: Store(object)

// directTest.Store
IL_0025:callvirt instance void ConsoleApplication38.ActualClass :: Store(object)

,因为方法槽是在ActualClass上定义的,所以产生相同的调用。



现在让我们看看如果我们自己写IL的话,使用我们想要的类型而不是让C#编译器为我们选择它。我修改了IL的外观如下:



对于界面:

  // actualTest.Store 
IL_0015:callvirt实例void ConsoleApplication38.IActualInterface :: Store(object)

// extendedTest.Store
IL_001d:callvirt instance void ConsoleApplication38。 IExtendedInterface :: Store(object)

// directTest.Store
IL_0025:callvirt实例void ConsoleApplication38.TestInterface :: Store(object)

对于类:

  // actualTest.Store 
IL_0015:callvirt实例void ConsoleApplication38.ActualClass :: Store(object)

// extendedTest.Store
IL_001d:callvirt实例void ConsoleApplication38.ExtendedClass :: Store(object)

// directTest.Store
IL_0025:callvirt实例void ConsoleApplication38.TestClass :: Store(object)

程序用ILasm编译得很好。但是它无法在运行时传递peverify和崩溃,并显示以下错误:


未处理的异常:
System.MissingMethodException:Method
not found:'Void
ConsoleApplication38.IExtendedInterface.Store(System.Object)'。
at
ConsoleApplication38.Program.TestInterfaces()
at
ConsoleApplication38.Program.Main(String []
args)


< blockquote>

如果你删除这个无效的调用,派生类调用工作正常没有任何错误。 CLR能够从派生类型调用中解析基本方法。但是接口在运行时没有真正的表示,并且CLR不能解析来自扩展接口的方法调用。



理论上,C#编译器可以发出直接调用在运行时指定的正确类。这将避免中间类调用的问题,如 Eric Lippert的博客。但是,如图所示,这对于接口是不可能的。



让我们回到DLR。它以与CLR完全相同的方式解析方法。我们已经看到CLR无法解析 IExtendedInterface.Store 。 DLR不能!这完全被C#编译器发出正确的调用所隐藏,因此在使用 dynamic 时一定要小心,除非你完全知道它在CLR中是如何工作的。 p>

I have the following interfaces that are part of an existing project. I'd like to make it possible to call the Store(..) function with dynamic objects. But I don't want to change the Interface hierarchy (if at all possible).

public interface IActualInterface
{
    void Store(object entity);    
}
public interface IExtendedInterface : IActualInterface
{
    //Interface items not important
}        
public class Test : IExtendedInterface 
{
    public void Store(object entity)
    {
        Console.WriteLine("Storing: " + entity.ToString());
    }       
}

and the following code:

IExtendedInterface extendedInterfaceTest = new Test();
IActualInterface actualInterfaceTest = new Test();
Test directTest = new Test();

dynamic employee = new ExpandoObject();
employee.Name = "John Smith";
employee.Age = 33;
employee.Phones = new ExpandoObject();
employee.Phones.Home = "0111 123123";
employee.Phones.Office = "027 321123";
employee.Tags = new List<dynamic>() { 123.4D, 99.54D };

try
{
    extendedInterfaceTest .Store(employee);
}
catch (RuntimeBinderException rbEx)
{
    Console.WriteLine(rbEx.Message);
}

//Casting as (object) works okay as it's not resolved at runtime
extendedInterfaceTest.Store((object)employee);

//this works because IActualInterface implements 'Store'
actualInterfaceTest.Store(employee);
//this also works okay (directTest : IProxyTest)
directTest.Store(employee);

When I call extendedInterfaceTest.Store(employee), it raises a runtime binder exception. Why does the interface type make a difference when it's the same underlying type? I can call it on IActualInterface and Type, but not IExtendedInterface?

I understand that when calling a function with a dynamic parameter, the resolution happens at runtime, but why the different behaviours?

解决方案

What you need to remember is that dynamic resolution basically does the same process as static resolution, but at runtime. Anything that couldn't be resolved by the CLR won't be resolved by the DLR.

Let's take this small program, inspired by yours, and that doesn't use dynamic at all:

namespace ConsoleApplication38 {

    public interface IActualInterface {
        void Store(object entity);
    }
    public interface IExtendedInterface : IActualInterface {
    }
    public class TestInterface : IExtendedInterface {
        public void Store(object entity) {
        }
    }

    public abstract class ActualClass {
        public abstract void Store(object entity);
    }
    public abstract class ExtendedClass : ActualClass { 
    }
    public class TestClass : ExtendedClass {
        public override void Store(object entity) {
        }
    }

    class Program {

        static void TestInterfaces() {
            IActualInterface actualTest = new TestInterface();
            IExtendedInterface extendedTest = new TestInterface();
            TestInterface directTest = new TestInterface();

            actualTest.Store(null);
            extendedTest.Store(null);
            directTest.Store(null);
        }

        static void TestClasses() {
            ActualClass actualTest = new TestClass();
            ExtendedClass extendedTest = new TestClass();
            TestClass directTest = new TestClass();

            actualTest.Store(null);
            extendedTest.Store(null);
            directTest.Store(null);
        }

        static void Main(string[] args) {
            TestInterfaces();
            TestClasses();
        }
    }
}

Everything compiles fine. But what did the compiler really generate? Let's see using ILdasm.

For the interfaces:

// actualTest.Store
IL_0015:  callvirt   instance void ConsoleApplication38.IActualInterface::Store(object)

// extendedTest.Store
IL_001d:  callvirt   instance void ConsoleApplication38.IActualInterface::Store(object)

// directTest.Store
IL_0025:  callvirt   instance void ConsoleApplication38.TestInterface::Store(object)

We can see here that the C# compiler always generates calls for the interface or class where the method is defined. IActualInterface has a method slot for Store so it's used for actualTest.Store. IExtendedInterface doesn't, so IActualInterface is used for the call. TestInterface defines a new method Store, using the newslot IL modifier, effectively assigning a new slot in the vtable for that method, so it's directly used since directTest is of type TestInterface.

For the classes:

// actualTest.Store
IL_0015:  callvirt   instance void ConsoleApplication38.ActualClass::Store(object)

// extendedTest.Store
IL_001d:  callvirt   instance void ConsoleApplication38.ActualClass::Store(object)

// directTest.Store
IL_0025:  callvirt   instance void ConsoleApplication38.ActualClass::Store(object)

For the 3 different types, the same call is generated because the method slot is defined on ActualClass.

Let's now see what we get if we write the IL ourselves, using the type we want rather than letting the C# compiler choosing it for us. I've modified the IL to look like this:

For interfaces:

// actualTest.Store
IL_0015:  callvirt   instance void ConsoleApplication38.IActualInterface::Store(object)

// extendedTest.Store
IL_001d:  callvirt   instance void ConsoleApplication38.IExtendedInterface::Store(object)

// directTest.Store
IL_0025:  callvirt   instance void ConsoleApplication38.TestInterface::Store(object)

For classes:

// actualTest.Store
IL_0015:  callvirt   instance void ConsoleApplication38.ActualClass::Store(object)

// extendedTest.Store
IL_001d:  callvirt   instance void ConsoleApplication38.ExtendedClass::Store(object)

// directTest.Store
IL_0025:  callvirt   instance void ConsoleApplication38.TestClass::Store(object)

The program compiles fine with ILasm. However it fails to pass peverify and crashes at runtime with the following error:

Unhandled Exception: System.MissingMethodException: Method not found: 'Void ConsoleApplication38.IExtendedInterface.Store(System.Object)'. at ConsoleApplication38.Program.TestInterfaces() at ConsoleApplication38.Program.Main(String[] args)

If you remove this invalid call, the derived classes calls work fine without any error. The CLR is able to resolve the base method from the derived type call. However interfaces have no true representation in runtime, and the CLR isn't able to resolve the method call from the extended interface.

In theory, the C# compiler could emit the call directly to the correct class specified in the runtime. It would avoid problems about middle classes calls as seen on Eric Lippert's blog. However as demonstrated, this is not possible for interfaces.

Let's get back to the DLR. It resolves the method exactly the same way as the CLR. We've seen that IExtendedInterface.Store couldn't be resolved by the CLR. The DLR cannot either! This is totally hidden by the fact that the C# compiler will emit the right call, so always be careful when using dynamic unless you perfectly know how it works in the CLR.

这篇关于使用动态类型作为方法参数时的奇怪行为的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆