Dynamic 用C#4对动态对象进行鸭式测试

Dynamic 用C#4对动态对象进行鸭式测试,dynamic,c#-4.0,duck-typing,Dynamic,C# 4.0,Duck Typing,我想要一个简单的C#duck类型示例,使用动态对象。在我看来,一个动态对象应该有HasValue/HasProperty/HasMethod方法,在尝试对其运行之前,它应该有一个字符串参数作为您要查找的值、属性或方法的名称。我试图避免尝试/捕捉障碍,如果可能的话,还要进行更深层次的思考。在动态语言(JS、Ruby、Python等)中,duck类型似乎是一种常见的做法,即在尝试使用属性/方法之前测试它,然后返回默认值,或者抛出受控异常。下面的例子基本上就是我想要完成的 如果上面描述的方法不存在,是

我想要一个简单的C#duck类型示例,使用动态对象。在我看来,一个动态对象应该有HasValue/HasProperty/HasMethod方法,在尝试对其运行之前,它应该有一个字符串参数作为您要查找的值、属性或方法的名称。我试图避免尝试/捕捉障碍,如果可能的话,还要进行更深层次的思考。在动态语言(JS、Ruby、Python等)中,duck类型似乎是一种常见的做法,即在尝试使用属性/方法之前测试它,然后返回默认值,或者抛出受控异常。下面的例子基本上就是我想要完成的

如果上面描述的方法不存在,是否有人为dynamic预先制作了扩展方法来实现这一点


示例:在JavaScript中,我可以相当容易地在对象上测试方法

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

在C#中我将如何做同样的事情

试试这个:

    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;
    }
或此(仅使用动态):


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

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

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

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

为每个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 {}       
    }
}
使用系统;
使用System.Collections.Generic;
使用System.Linq;
使用系统文本;
运用系统动力学;
使用Microsoft.CSharp.RuntimeBinder;
使用System.Linq.Expressions;
使用System.Runtime.CompilerServices;
命名空间DynamicCheckPropertyExistence
{
班级计划
{        
静态void Main(字符串[]参数)
{
dynamic testDynamicObject=新的ExpandooObject();
testDynamicObject.Name=“Testovaci-vlastnot”;
WriteLine(HasProperty(testDynamicObject,“Name”);
WriteLine(HasProperty(testDynamicObject,“Id”);
Console.ReadLine();
}
私有静态bool HasProperty(IDynamicmMetaObjectProvider dynamicProvider,字符串名称)
{
var defaultBinder=Binder.GetMember(CSharpBinderFlags.None,名称,typeof(程序),
新[]
{
CSharpArgumentInfo.Create(
CSharpArgumentInfoFlags.None,null)
})作为活页夹;
var callSite=callSite.Create(new NoThrowGetBinderMember(name,false,defaultBinder));
var result=callSite.Target(callSite,dynamicProvider);
if(Object.ReferenceEquals(result,NoThrowExpressionVisitor.DUMMY_result))
{
返回false;
}
返回true;
}
}
类NoThrowGetBinderMember:GetMemberBinder
{
私人GetMemberBinder m_innerBinder;
public NoThrowGetBinderMember(字符串名称,bool ignoreCase,GetMemberBinder innerBinder):基(名称,ignoreCase)
{
m_innerBinder=innerBinder;
}
公共覆盖DynamicMetaObject FallbackGetMember(DynamicMetaObject目标,DynamicMetaObject错误建议)
{
var retMetaObject=m_innerBinder.Bind(目标,新的DynamicMetaObject[]{});
var noThrowVisitor=new NoThrowExpressionVisitor();
var resultExpression=noThrowVisitor.Visit(retMetaObject.Expression);
var finalMetaObject=新的DynamicMetaObject(resultExpression,retMetaObject.Restrictions);
返回最终对象;
}
}
类NoThrowExpressionVisitor:ExpressionVisitor
{        
公共静态只读对象DUMMY_RESULT=新DummyBindingResult();
public NoThrowExpressionVisitor()
{
}
受保护的重写表达式访问条件(条件表达式节点)
{
if(node.IfFalse.NodeType!=ExpressionType.Throw)
{
返回base.VisitConditional(节点);
}
表达式dummyFalseResult=()=>DUMMY_结果;
var invokeDummyFalseResult=Expression.Invoke(dummyFalseResult,null);
返回表达式.Condition(node.Test、node.IfTrue、invokeDummyFalseResult);
}
私有类DummyBindingResult{}
}
}

似乎是一个很好的动态对象界面映射器。。。这比我希望的要多一些,但似乎是所展示示例的最干净的实现。。。保持Simon的答案正确,因为它仍然是最接近我想要的,但即兴界面方法非常好。

我认为这是解决方案的一半。如果duck under是一个普通的CLR对象,它就可以工作。如果它是来自某一DLR语言的动态类型,或者它是实现IDynamicMetaObjectProvider接口的对象,CLR将在诉诸反射之前首先尝试绑定到该对象。关于如何检查方法存在性的任何其他建议?我知道try/catch会起作用,但是反射可能更接近我需要的,也许是一个扩展
Can a duck quack?
QUACK!
Can a cow quack?
... silence ...
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 {}       
    }
}