Тестирование типов утиных с C # 4 для динамических объектов

Я хочу иметь простой пример набора текста на C # с использованием динамических объектов. Мне кажется, что динамический объект должен иметь методы HasValue / HasProperty / HasMethod с одним строковым параметром для имени значения, свойства или метода, которые вы ищете, прежде чем пытаться с ним работать. Я стараюсь избегать блоков try / catch и, если возможно, более глубокого размышления. Просто кажется, что это обычная практика для утиной печати на динамических языках (JS, Ruby, Python и т. Д.), То есть проверка свойства / метода перед попыткой его использования, а затем возврат к значениям по умолчанию или выдача управляемого исключения. . Пример ниже - это в основном то, что я хочу достичь.

Если методы, описанные выше, не существуют, есть ли у кого-нибудь готовые методы расширения для динамических, которые будут делать это?


Пример: в JavaScript я могу довольно легко проверить метод объекта.

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


Как мне сделать то же самое на 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
}

person Tracker1    schedule 06.06.2010    source источник
comment
Так же, как примечание для всех, кто ищет ... ExpandoObject (не уверен в других) реализует IDictionary ‹строка, объект›, поэтому вы можете протестировать с помощью var myDynamicAsDictionary = myDyn как IDictionary ‹строка, объект›; затем проверьте значение null и .HasKey ()   -  person Tracker1    schedule 17.12.2010
comment
@nawfal мой был на 2 дня раньше, чем тот, с которым вы связались ... Я просто подумал, что можно было бы создать такие методы проверки с подписями общего типа ... Duck.HasFunc<TRet, T1>(string name) в качестве примера подписи ... Я не использую C # на этом уровне уже нет, но было бы интересно.   -  person Tracker1    schedule 12.08.2015
comment
Я вижу это. Иногда можно закрыть более старый вопрос, если новый вопрос получил больше внимания. Но я понимаю вашу точку зрения.   -  person nawfal    schedule 13.08.2015


Ответы (5)


Попробуй это:

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

Или это (использует только динамический):

    public static dynamic Quack(dynamic duck)
    {
        try
        {
            //invoke and return value
            return duck.Quack();
        }
        //thrown if method call failed
        catch (RuntimeBinderException)
        {
            return null;
        }        
    }
person Simon    schedule 06.06.2010
comment
Думаю, это половина решения. Это работает, если утка внизу - это простой объект CLR. Если это динамический тип, поступающий из одного из языков DLR, или это объект, реализующий интерфейс IDynamicMetaObjectProvider, среда CLR сначала попытается выполнить привязку к нему, прежде чем прибегать к отражению. - person driis; 06.06.2010
comment
Есть ли другие предложения о том, как проверить существование метода? - person Simon; 06.06.2010
comment
Я знаю, что try / catch сработает, но отражение, вероятно, ближе к тому, что мне нужно, возможно, метод расширения динамически переносится для проверок, просто трудно поверить, что для этого еще нет чего-то. +1 от меня, все еще надеюсь на лучший ответ, и, возможно, придется немного поиграть со всем этим. Желая избежать накладных расходов, связанных с преждевременным блоком try / catch, в отличие от конкретной проверки, как это делается в других сценариях утиной печати. - person Tracker1; 06.06.2010
comment
Хорошо, отметив это как правильный ответ, как предложил Эндрю Андерсон, вероятно, захочется обернуть это в метод расширения, просто надеялся на лучшую динамическую печать по умолчанию. Довольно часто проверять метод Quack перед вызовом Quack, чтобы перехватить исключение ... Кто-нибудь знает, сколько стоит отражение за простой перехват исключения? особенно если вы знаете, что получите исключение, скажем, в 50% случаев? - person Tracker1; 17.06.2010
comment
Обратите внимание, что пространство имен Microsoft.CSharp.RuntimeBinder не используется по умолчанию, по крайней мере, в моей установке LINQPad и Visual Studio 2010. Вам придется вручную добавить using ... для него или квалифицировать RuntimeBinderException полностью, как в Microsoft.CSharp.RuntimeBinder.RuntimeBinderException, чтобы второй solutino работал. - person Reb.Cabin; 20.08.2012

Если у вас есть контроль над всеми типами объектов, которые вы будете использовать динамически, другой вариант - заставить их наследовать от подкласса класса DynamicObject, который настроен так, чтобы не давать сбоев при вызове несуществующего метода:

Быстрая и грязная версия будет выглядеть так:

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

Затем вы можете сделать следующее:

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 ...";
    }
}

И ваш результат будет:

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

Изменить: я должен отметить, что это верхушка айсберга, если вы можете использовать этот подход и опираться на DynamicObject. Вы можете написать такие методы, как bool HasMember(string memberName), если хотите.

person Andrew Anderson    schedule 06.06.2010
comment
+1 для меня ... Ответ Саймона ближе к тому, что я хочу ... нужно будет обернуть в метод расширения HasMethod, но должен быть в состоянии сделать это ... просто надеялся на большее из коробки. - person Tracker1; 17.06.2010

Реализация метода HasProperty для каждого IDynamicMetaObjectProvider БЕЗ выброса 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 {}       
    }
}
person Rene Stein    schedule 23.08.2010

http://code.google.com/p/impromptu-interface/ Кажется, быть хорошим средством отображения интерфейса для динамических объектов ... Это немного больше работы, чем я ожидал, но, похоже, это самая чистая реализация из представленных примеров ... Сохранение ответа Саймона как правильного, поскольку он все еще наиболее близок к тому, что Я хотел, но методы интерфейса Impromptu действительно хороши.

person Tracker1    schedule 22.09.2011

Самый короткий путь - вызвать его и обработать исключение, если метод не существует. Я пришел из Python, где такой метод распространен в утиной печати, но я не знаю, широко ли он используется в C # 4 ...

Я не тестировал себя, так как на моем компьютере нет VC 2010

dynamic Quack(dynamic duck)
{
    try
    {
        return duck.Quack();
    }
    catch (RuntimeBinderException)
    { return null; }
}
person CharlesB    schedule 06.06.2010