Ayende @ Rahien

It's a girl

Challenge: Dynamically dynamic

Can you figure out a way to write the following code without using a try/catch?

class Program
{
    static void Main(string[] args)
    {
        dynamic e = new ExpandoObject();
        e.Name = "Ayende";

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

    private static bool HasProperty(string name, IDynamicMetaObjectProvider dyn)
    {
        try
        {
            var callSite =
                CallSite<Func<CallSite, object, object>>.Create(
                    Binder.GetMember(CSharpBinderFlags.None, name, typeof (Program),
                                     new[]
                                     {
                                         CSharpArgumentInfo.Create(
                                             CSharpArgumentInfoFlags.None, null)
                                     }));
            callSite.Target(callSite, dyn);
            return true;
        }
        catch (RuntimeBinderException)
        {

            return false;
        }
    }
}

The HasProperty method should accept any IDynamicMetaObjectProvider implementation, not just ExpandoObject.

Comments

Doug
06/23/2010 02:25 PM by
Doug
class Program

{

    static void Main(string[] args)

    {

        dynamic e = new ExpandoObject();

        e.Name = "John";


        Console.WriteLine(HasProperty("Name", e));

        Console.WriteLine(HasProperty("Test", e));

    }


    private static bool HasProperty(string name, IDynamicMetaObjectProvider dyn)

    {

        return ((IDictionary

<string,> )dyn).ContainsKey(name);

    }

}
Ayende Rahien
06/23/2010 02:28 PM by
Ayende Rahien

Doug,

Did you missed the part where I explicitly said that I wanted a solution that would work for things other than expando object?

Andrew Davey
06/23/2010 02:38 PM by
Andrew Davey

This seems to work for me. Not really done much stuff with dynamic however - so it might be silly!

private static bool HasProperty(string name, IDynamicMetaObjectProvider dyn)

{

return dyn.GetMetaObject(Expression.Constant(dyn)).GetDynamicMemberNames().Contains(name);

}

Steve
06/23/2010 02:50 PM by
Steve

The only problem is that dynamic providers have to fill the GetDynamicMemberNames() themself. So it's possible that the original code will correctly return true while your code will return false.

The method will just return new string[0] by default, and it's ExpandoObject that just returns all known keys there.

Matt Warren
06/23/2010 03:38 PM by
Matt Warren

I'm guessing this is a "fix" to the patch I just submitted ;-)

Ayende Rahien
06/23/2010 03:39 PM by
Ayende Rahien

Matt,

Sort of, I can't figure out a good way to do that.

Matt Warren
06/23/2010 03:51 PM by
Matt Warren

Is the try/catch method not a workable solution?

Ayende Rahien
06/23/2010 04:08 PM by
Ayende Rahien

It is, but I don't like it

ElSillo
06/23/2010 11:01 PM by
ElSillo

Sounds so simple to be silly, but waht about TryGetMember ?

David
06/25/2010 06:53 PM by
David

I think the point was to avoid putting it in a Try/Catch. I'm guessing this is for RavenDB, try/catch constructs are expensive.

Teleo
06/30/2010 03:29 PM by
Teleo

@Ayende,

By the way, I saw that RavenDB uses CodeDom to compile its indexes. My guess is that you chose this approach due to lack of good alternatives of code generation with 'dynamic' and expando objects.

What do you think about the fact that with each compilation CodeDom generates assemblies that remain loaded in the app domain?

Thanks,

T

Ayende Rahien
06/30/2010 04:01 PM by
Ayende Rahien

Teleo,

That is okay by me, since indexes are rarely changing.

Frank Quednau
07/16/2010 02:02 PM by
Frank Quednau

@Justin,

I had a couple of issues with your code, I have developed it further into this gist: http://gist.github.com/478382 . Based on reflection it can now also check existence of methods and property setters.

Rene Stein
08/17/2010 09:15 PM by
Rene Stein

What about this? Dirty, but fully functional draft.

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 e = new ExpandoObject();

        e.Name = "Ayende";


        Console.WriteLine(HasProperty("Name", e));

        Console.WriteLine(HasProperty("Id", e));

        Console.ReadLine();

    }


    private static bool HasProperty(string name, IDynamicMetaObjectProvider dyn)

    {




        var binder = Binder.GetMember(CSharpBinderFlags.None, name, typeof(Program),

                         new[]

                                 {

                                     CSharpArgumentInfo.Create(

                                     CSharpArgumentInfoFlags.None, null)

                                 }) as GetMemberBinder;



        var callSite =

                    CallSite

<func<callsite,>

.Create(new SpecialBinderMember(name, false, binder));

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


        if (Object.ReferenceEquals(result, NoThrowExpressionVisitor.DUMMY_RESULT))

        {

            return false;

        }


        return true;


    }

}


class SpecialBinderMember : GetMemberBinder

{

    private GetMemberBinder m_innerBinder;        

    public SpecialBinderMember(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 resultExpression = new NoThrowExpressionVisitor().Visit(retMetaObject.Expression);


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

        return finalMetaObject;


    }


}


class NoThrowExpressionVisitor : ExpressionVisitor

{        

    public const string DUMMY_RESULT = "

<undefined";

    public NoThrowExpressionVisitor()

    {


    }


    protected override Expression VisitConditional(ConditionalExpression node)

    {


        if (node.IfFalse.NodeType != ExpressionType.Throw)

        {

            return base.VisitConditional(node);

        }


        Expression

<func dummyFalseResult = () => DUMMY_RESULT;

        var invokeDummyFalseResult = Expression.Invoke(dummyFalseResult, null);                                    

        return Expression.Condition(node.Test, node.IfTrue, invokeDummyFalseResult);

    }


}

}

Rene Stein
08/17/2010 09:25 PM by
Rene Stein

Constant DUMMY_RESULT has value "undefined"

public const string DUMMY_RESULT = "";

Comments have been closed on this topic.