33 votes

Test de type canard avec C # 4 pour les objets dynamiques

Je suis désireux d'avoir un simple duck-typing exemple en C# en utilisant des objets dynamiques. Il me semble, qu'un objet dynamique devrait avoir HasValue/HasProperty/HasMethod méthodes avec une seule chaîne de caractère en paramètre le nom de la valeur, la propriété ou la méthode que vous recherchez avant d'essayer d'exécuter contre elle. J'essaie d'éviter les blocs try/catch, et un approfondissement de la réflexion, si possible. Il semble juste être une pratique courante pour de canard en tapant dans la dynamique des langues (JS, Ruby, Python, etc) qui est à l'essai pour une propriété/méthode avant d'essayer de l'utiliser, puis revenir à un défaut ou à lancer une exception contrôlée. L'exemple ci-dessous est en gros ce que je veux accomplir.

Si les méthodes décrites ci-dessus n'existent pas, quelqu'un aurait-il premade les méthodes d'extension pour la dynamique qui va le faire?


Exemple: En JavaScript, je peux le tester pour une méthode sur un objet assez facilement.

//JavaScript
function quack(duck) {
  if (duck && typeof duck.quack === "function") {
    return duck.quack();
  }
  return null; //nothing to return, not a duck
}


Comment pourrais-je faire la même chose en C#?

//C# 4
dynamic Quack(dynamic duck)
{
  //how do I test that the duck is not null, 
  //and has a quack method?

  //if it doesn't quack, return null
}

16voto

Andrew Anderson Points 1823

Si vous avez le contrôle sur tous les types d'objet qui vous permettra d'utiliser de façon dynamique, une autre option serait de les forcer à hériter d'une sous-classe de la DynamicObject de la classe qui est adapté pour ne pas échouer lorsqu'une méthode qui n'existe pas est appelé:

Un moyen rapide et sale version devrait ressembler à ceci:

public class DynamicAnimal : DynamicObject
{
    public override bool TryInvokeMember(InvokeMemberBinder binder, object[] args, out object result)
    {
        bool success = base.TryInvokeMember(binder, args, out result);

        // If the method didn't exist, ensure the result is null
        if (!success) result = null;

        // Always return true to avoid Exceptions being raised
        return true;
    }
}

Vous pouvez ensuite effectuer les opérations suivantes:

public class Duck : DynamicAnimal
{
    public string Quack()
    {
        return "QUACK!";
    }
}

public class Cow : DynamicAnimal
{
    public string Moo()
    {
        return "Mooooo!";
    }
}
class Program
{
    static void Main(string[] args)
    {
        var duck = new Duck();
        var cow = new Cow();

        Console.WriteLine("Can a duck quack?");
        Console.WriteLine(DoQuack(duck));
        Console.WriteLine("Can a cow quack?");
        Console.WriteLine(DoQuack(cow));
        Console.ReadKey();
    }

    public static string DoQuack(dynamic animal)
    {
        string result = animal.Quack();
        return result ?? "... silence ...";
    }
}

Et votre sortie serait:

Can a duck quack?
QUACK!
Can a cow quack?
... silence ...

Edit: je tiens à noter que c'est la partie émergée de l'iceberg si vous êtes en mesure d'utiliser cette approche et de miser sur DynamicObject. Vous pouvez écrire des méthodes comme bool HasMember(string memberName) si vous le souhaité.

13voto

Simon Points 5327

Essaye ça:

     using System.Linq;
    using System.Reflection;
    //...
    public dynamic Quack(dynamic duck, int i)
    {
        Object obj = duck as Object;

        if (duck != null)
        {
            //check if object has method Quack()
            MethodInfo method = obj.GetType().GetMethods().
                            FirstOrDefault(x => x.Name == "Quack");

            //if yes
            if (method != null)
            {

                //invoke and return value
                return method.Invoke((object)duck, null);
            }
        }

        return null;
    }
 

Ou ceci (utilise uniquement dynamique):

     public static dynamic Quack(dynamic duck)
    {
        try
        {
            //invoke and return value
            return duck.Quack();
        }
        //thrown if method call failed
        catch (RuntimeBinderException)
        {
            return null;
        }        
    }
 

3voto

Rene Stein Points 21

Implémentation de la méthode HasProperty pour chaque IDynamicMetaObjectProvider SANS lancer RuntimeBinderException.

 using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Dynamic;
using Microsoft.CSharp.RuntimeBinder;
using System.Linq.Expressions;
using System.Runtime.CompilerServices;


namespace DynamicCheckPropertyExistence
{
    class Program
    {        
        static void Main(string[] args)
        {
            dynamic testDynamicObject = new ExpandoObject();
            testDynamicObject.Name = "Testovaci vlastnost";

            Console.WriteLine(HasProperty(testDynamicObject, "Name"));
            Console.WriteLine(HasProperty(testDynamicObject, "Id"));            
            Console.ReadLine();
        }

        private static bool HasProperty(IDynamicMetaObjectProvider dynamicProvider, string name)
        {



            var defaultBinder = Binder.GetMember(CSharpBinderFlags.None, name, typeof(Program),
                             new[]
                                     {
                                         CSharpArgumentInfo.Create(
                                         CSharpArgumentInfoFlags.None, null)
                                     }) as GetMemberBinder;


            var callSite = CallSite<Func<CallSite, object, object>>.Create(new NoThrowGetBinderMember(name, false, defaultBinder));


            var result = callSite.Target(callSite, dynamicProvider);

            if (Object.ReferenceEquals(result, NoThrowExpressionVisitor.DUMMY_RESULT))
            {
                return false;
            }

            return true;

        }



    }

    class NoThrowGetBinderMember : GetMemberBinder
    {
        private GetMemberBinder m_innerBinder;        

        public NoThrowGetBinderMember(string name, bool ignoreCase, GetMemberBinder innerBinder) : base(name, ignoreCase)
        {
            m_innerBinder = innerBinder;            
        }

        public override DynamicMetaObject FallbackGetMember(DynamicMetaObject target, DynamicMetaObject errorSuggestion)
        {


            var retMetaObject = m_innerBinder.Bind(target, new DynamicMetaObject[] {});            

            var noThrowVisitor = new NoThrowExpressionVisitor();
            var resultExpression = noThrowVisitor.Visit(retMetaObject.Expression);

            var finalMetaObject = new DynamicMetaObject(resultExpression, retMetaObject.Restrictions);
            return finalMetaObject;

        }

    }

    class NoThrowExpressionVisitor : ExpressionVisitor
    {        
        public static readonly object DUMMY_RESULT = new DummyBindingResult();

        public NoThrowExpressionVisitor()
        {

        }

        protected override Expression VisitConditional(ConditionalExpression node)
        {

            if (node.IfFalse.NodeType != ExpressionType.Throw)
            {
                return base.VisitConditional(node);
            }

            Expression<Func<Object>> dummyFalseResult = () => DUMMY_RESULT;
            var invokeDummyFalseResult = Expression.Invoke(dummyFalseResult, null);                                    
            return Expression.Condition(node.Test, node.IfTrue, invokeDummyFalseResult);
        }

        private class DummyBindingResult {}       
    }
}
 

3voto

Tracker1 Points 6573

http://code.google.com/p/impromptu-interface/ Semble être un joli mappeur d'interface pour les objets dynamiques ... C'est un peu plus de travail que ce que j'espérais, mais semble être la mise en œuvre la plus propre des exemples présenté ... Garder la réponse de Simon aussi correcte, car elle est toujours la plus proche de ce que je voulais, mais les méthodes d'interface Impromptu sont vraiment sympas.

1voto

CharlesB Points 27070

Le chemin le plus court serait de l'invoquer et de gérer l'exception si la méthode n'existe pas. Je viens de Python où une telle méthode est courante en typage de canard, mais je ne sais pas si elle est largement utilisée en C # 4 ...

Je ne me suis pas testé depuis que je n'ai pas VC 2010 sur ma machine

 dynamic Quack(dynamic duck)
{
    try
    {
        return duck.Quack();
    }
    catch (RuntimeBinderException)
    { return null; }
}
 

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