38 votes

Comportement étrange lors de l'utilisation de types dynamiques comme paramètres de méthode

J'ai les interfaces suivantes qui font partie d'un projet existant. Je souhaiterais qu'il soit possible d'appeler le Magasin(..) la fonction avec les objets dynamiques. Mais je ne veux pas changer l'Interface de la hiérarchie (si 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());
    }       
}

et le code suivant:

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);

Quand je l'appelle extendedInterfaceTest.Store(employee), elle soulève une exécution liant exception. Pourquoi le type d'interface de faire une différence quand c'est le même type sous-jacent? Je peux l'appeler sur IActualInterface et Type, mais pas IExtendedInterface?

Je comprends que lorsque l'appel d'une fonction avec un paramètre dynamique, la résolution se produit au moment de l'exécution, mais pourquoi les différents comportements?

87voto

Julien Lebosquain Points 20894

Ce que vous devez retenir, c'est que la résolution dynamique essentiellement le même processus que la statique de la résolution, mais au moment de l'exécution. Tout ce qui ne pouvait pas être résolu par le CLR ne sera pas résolu par le DLR.

Prenons ce petit programme, inspiré par la vôtre, et qui n'utilise pas dynamique du tout:

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();
        }
    }
}

Tout compile bien. Mais ce que fait le compilateur vraiment générer? Nous allons voir à l'aide de ILdasm.

Pour les 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)

Nous pouvons voir ici que le compilateur C# génère toujours des appels de l'interface ou de la classe dans laquelle la méthode est définie. IActualInterface a une méthode logement pour le Magasin de sorte qu'il est utilisé pour l' actualTest.Store. IExtendedInterface n'est pas le cas, IActualInterface est utilisé pour l'appel. TestInterface définit une nouvelle méthode de Magasin, à l'aide de l' newslot IL modificateur, effectivement l'attribution d'un nouveau logement dans la vtable pour cette méthode, il est donc utilisé directement depuis directTest est de type TestInterface.

Pour les 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)

Pour les 3 types différents, le même appel est généré en raison de la méthode de logement est défini sur ActualClass.

Nous allons maintenant voir ce que nous obtenons si nous écrivons le IL de nous-mêmes, en utilisant le type que nous voulons plutôt que de laisser le compilateur C# qui le choisissent pour nous. J'ai modifié le IL ressemble à ceci:

Pour les 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)

Pour les 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)

Le programme compile bien avec ILasm. Cependant, il ne parvient pas à passer peverify et se bloque lors de l'exécution avec l'erreur suivante:

Exception Non Gérée: Système.MissingMethodException: Méthode pas trouvé: "Void ConsoleApplication38.IExtendedInterface.Magasin(Le Système.Objet)'. au ConsoleApplication38.Programme.TestInterfaces() au ConsoleApplication38.Programme.Main(String[] args)

Si vous supprimez cette invalide appel, les classes dérivées appels fonctionne très bien sans aucune erreur. Le CLR est en mesure de résoudre à la base de la méthode de la dérivée de type d'appel. Cependant, les interfaces ont pas de véritable représentation dans l'exécution, et le CLR n'est pas en mesure de résoudre l'appel de méthode à partir de l'interface étendue.

En théorie, le compilateur C# pourrait émettre l'appel directement à la bonne classe spécifiée dans le runtime. Il permettrait d'éviter les problèmes sur les classes moyennes appelle comme on le voit sur Eric Lippert du blog. Cependant, comme l'ont démontré, ce n'est pas possible pour des interfaces.

Revenons à la DLR. Il résout la méthode exactement de la même manière que le CLR. Nous avons vu que l' IExtendedInterface.Store ne pouvait pas être résolu par le CLR. Le DLR ne peut pas non plus! C'est totalement masqué par le fait que le compilateur C# émet le droit d'appel, il faut donc toujours être prudent lors de l'utilisation d' dynamic , sauf si vous savez parfaitement comment cela fonctionne dans le CLR.

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X