【问题标题】:Duck type testing with C# 4 for dynamic objects使用 C# 4 对动态对象进行 Duck 类型测试
【发布时间】:2011-02-28 10:25:29
【问题描述】:

我想在 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
}

【问题讨论】:

  • 就像给正在看的人的注释...然后测试 null 和 .HasKey()
  • @nawfal 我的比您链接的那个早 2 天...我只是在想可能可以使用泛型类型签名创建这样的检查方法...以Duck.HasFunc<TRet, T1>(string name) 为例签名...我不再在这个级别使用 C#,但它会很有趣。
  • 我明白了。有时,如果较新的问题受到更多关注,则可以关闭较旧的问题。但我明白你的意思。

标签: dynamic c#-4.0 duck-typing


【解决方案1】:

试试这个:

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

【讨论】:

  • 我认为这是解决方案的一半。如果下面的鸭子是一个普通的 CLR 对象,它就可以工作。如果它是来自某种 DLR 语言的动态类型,或者它是实现 IDynamicMetaObjectProvider 接口的对象,CLR 将首先尝试绑定到该接口,然后再诉诸反射。
  • 关于如何检查方法是否存在的任何其他建议?
  • 我知道 try/catch 会起作用,但反射可能更接近我所需要的,也许是动态包装的扩展方法,用于检查,很难相信已经没有东西了为此。来自我的+1,仍然希望得到更好的答案,并且可能只需要玩一下。想要避免过早的 try/catch 块的开销,而不是像在其他鸭子类型场景中所做的特定检查。
  • 好的,按照 Andrew Anderson 的建议,将其标记为正确答案,可能希望将其包装到扩展方法中,只是希望默认情况下更好地输入动态。在捕获异常之前调用“Quack”之前测试“Quack”方法是很常见的......任何人都知道反射的成本是什么?特别是如果你知道你会在 50% 的时间里得到一个例外?
  • 请注意,默认情况下不使用命名空间Microsoft.CSharp.RuntimeBinder,至少在我安装的LINQPad和Visual Studio 2010中是这样。您必须手动为其添加using ...或限定@987654325 @ 与 Microsoft.CSharp.RuntimeBinder.RuntimeBinderException 完全一样,第二个解决方案就可以工作了。
【解决方案2】:

最短路径是调用它,如果方法不存在则处理异常。我来自Python,这种方法在duck-typing中很常见,但我不知道它是否在C#4中被广泛使用......

我没有测试自己,因为我的机器上没有 VC 2010

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

【讨论】:

    【解决方案3】:

    如果您可以控制将要动态使用的所有对象类型,另一种选择是强制它们从 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) 这样的方法。

    【讨论】:

    • +1 对我来说......西蒙的答案更接近我想要的......需要包装成 HasMethod 扩展方法,但应该能够做到......只是希望更多的盒子。
    【解决方案4】:

    为每个 IDynamicMetaObjectProvider 实现 HasProperty 方法,而不抛出 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 {}       
        }
    }
    

    【讨论】:

      【解决方案5】:

      http://code.google.com/p/impromptu-interface/ 似乎是一个不错的动态对象接口映射器...这比我希望的要多一些工作,但似乎是所提供示例的最干净的实现...保持西蒙的答案正确,因为它仍然是最接近我想要的,但是 Impromptu 接口方法非常好。

      【讨论】:

        猜你喜欢
        • 2014-10-25
        • 1970-01-01
        • 2014-01-13
        • 2011-07-13
        • 2022-01-23
        • 2020-07-04
        • 1970-01-01
        • 1970-01-01
        • 2013-09-13
        相关资源
        最近更新 更多