Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Support for dynamic object parameter/variable (ExpandoObject) #16

Closed
davideicardi opened this issue Feb 24, 2014 · 10 comments
Closed

Support for dynamic object parameter/variable (ExpandoObject) #16

davideicardi opened this issue Feb 24, 2014 · 10 comments
Milestone

Comments

@davideicardi
Copy link
Member

See also:
http://stackoverflow.com/questions/21982040/using-dynamic-types-with-espresso

@davideicardi davideicardi added this to the 1.0 milestone Feb 24, 2014
@davideicardi davideicardi modified the milestone: 1.0 May 10, 2014
@AdamsLair
Copy link

👍

Any news on this?

@davideicardi
Copy link
Member Author

No, sorry. For now I don't have any news. I have tried to post a question on stackoverflow but without success:
http://stackoverflow.com/questions/23175214/correct-way-to-construct-net-expression-to-invoke-dynamic-objects

I have found a way to solve this only using unsupported/private methods. And I really don't like it.

@AdamsLair
Copy link

Have you tried deriving from the public (abstract) class DynamicMetaObjectBinder in the System.Dynamic namespace and just see what kinds of calls you get? It's from the same relatively small namespace as both DynamicObject and ExpandoObject, so it might be worth a try.

Also, I have done some research. Consider the following test program:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Linq.Expressions;

namespace TestProject
{
    public class Program
    {
        public static void Test()
        {
            dynamic test = "Hello";
            object result = test.Length;
            Console.WriteLine(result);
        }
    }
}

When decompiled using ILSpy, what you get is this:

using Microsoft.CSharp.RuntimeBinder;
using System;
using System.Linq.Expressions;
using System.Runtime.CompilerServices;
namespace TestProject
{
    public class Program
    {
        [CompilerGenerated]
        private static class <Test>o__SiteContainer0
        {
            public static CallSite<Func<CallSite, object, object>> <>p__Site1;
        }
        public static void Test()
        {
            object test = "Hello";
            if (Program.<Test>o__SiteContainer0.<>p__Site1 == null)
            {
                Program.<Test>o__SiteContainer0.<>p__Site1 = CallSite<Func<CallSite, object, object>>.Create(Binder.GetMember(CSharpBinderFlags.None, "Length", typeof(Program), new CSharpArgumentInfo[]
                {
                    CSharpArgumentInfo.Create(CSharpArgumentInfoFlags.None, null)
                }));
            }
            object result = Program.<Test>o__SiteContainer0.<>p__Site1.Target(Program.<Test>o__SiteContainer0.<>p__Site1, test);
            Console.WriteLine(result);
        }
    }
}

Which appears to reveal the inner workings of the dynamic keyword. I've manually edited this to make it more readable and remove insignificant parts:

public class Program
{
    [CompilerGenerated]
    private static class Container
    {
        public static CallSite<Func<CallSite, object, object>> Site;
    }
    public static void Test()
    {
        object test = "Hello";
        if (Program.Container.Site == null)
        {
            Program.Container.Site = CallSite<Func<CallSite, object, object>>.Create(
                Binder.GetMember(
                    CSharpBinderFlags.None, 
                    "Length", 
                    typeof(Program), 
                    new CSharpArgumentInfo[]
                    {
                        CSharpArgumentInfo.Create(CSharpArgumentInfoFlags.None, null)
                    }));
        }
        object result = Program.Container.Site.Target(Program.Container.Site, test);
        Console.WriteLine(result);
    }
}

Now I'm assuming that we're dealing with two things here:

  1. The actual dynamic binding code.
  2. Some kind of caching to prevent it from running each time something is accessed.

I would think that the CallSite stuff is related to the caching mechanism, so let's strip that away:

public static void Test()
{
    object test = "Hello";
    var callSite = CallSite<Func<CallSite, object, object>>.Create(
        Binder.GetMember(
            CSharpBinderFlags.None, 
            "Length", 
            typeof(Program), 
            new CSharpArgumentInfo[]
            {
                CSharpArgumentInfo.Create(CSharpArgumentInfoFlags.None, null)
            }));
    object result = callSite.Target(callSite, test);
    Console.WriteLine(result);
}

Digging into the CallSite code reveals that the Create method actually requires a CallSiteBinder, just like the one required by Expression.Dynamic, and that Binder is not the one from System.Reflection, but rather Microsoft.CSharp.RuntimeBinder a public static class from the .Net Framework - but as you said on StackOverflow:

This API supports the .NET Framework infrastructure and is not intended to be used directly from your code.

However, what it does (thanks ILSpy again :D ) appears to be nothing more than creating a CSharpGetMemberBinder which derives from GetMemberBinder, which derives from DynamicMetaObjectBinder - so we're back at the beginning. I totally get why you didn't implement this feature yet, this is just one jungle of obscure API and lacking documentation.

@RupertAvery
Copy link

My similar project CSharpEval supports dynamics. I also used ILSpy to get a gist of what the compiler was doing. It probably isn't perfect, but it works. You just put the binder you created in an Expression.Dynamic.

Property Getter:

                var binder = Binder.GetMember(
                    CSharpBinderFlags.None,
                    membername,
                    type,
                    new[] { CSharpArgumentInfo.Create(CSharpArgumentInfoFlags.None, null) }
                    );

                Expression result = Expression.Dynamic(binder, typeof(object), instance);

Property Setter (upon checking that the left hand expression is a dynamic):

                var dle = (DynamicExpression)le;
                var membername = ((GetMemberBinder)dle.Binder).Name;
                var instance = dle.Arguments[0];

                var binder = Binder.SetMember(
                    CSharpBinderFlags.None,
                    membername,
                    type,
                    new[]
                        {
                            CSharpArgumentInfo.Create(CSharpArgumentInfoFlags.None, null),
                            CSharpArgumentInfo.Create(CSharpArgumentInfoFlags.None, null)
                        }
                    );

                return Expression.Dynamic(binder, typeof(object), instance, re);

Method Call:

                var binderM = Binder.InvokeMember(
                    CSharpBinderFlags.None,
                    membername,
                    null,
                    type,
                    expArgs.Select(x => CSharpArgumentInfo.Create(CSharpArgumentInfoFlags.None, null))
                    );

                return Expression.Dynamic(binderM, typeof(object), expArgs);

You also have to catch unary and binary expressions and handle dynamics accordingly:

Binary:

            var expArgs = new List<Expression>() { le, re };

            var binderM = Binder.BinaryOperation(CSharpBinderFlags.None, expressionType, le.Type,
                                                 new CSharpArgumentInfo[]
                                                     {
                                                         CSharpArgumentInfo.Create(CSharpArgumentInfoFlags.None, null),
                                                         CSharpArgumentInfo.Create(CSharpArgumentInfoFlags.None, null)
                                                     });

            return Expression.Dynamic(binderM, typeof(object), expArgs);

All this is in the AntlrParser branch, ExpressionEvaluator project, ExpressionHelper.cs. My code is terrible, btw, but hopefully you can get some information from it.

@davideicardi davideicardi added this to the 1.5 milestone Mar 5, 2015
@davideicardi
Copy link
Member Author

Thank you Adam and Rupert for your comments! I really appreciate it.

I usually don't like to use code marked as

"This API supports the .NET Framework infrastructure and is not intended to be used directly from your code."

So my idea is to support some kind of extension mechanism used to plugin special/custom implementations. I will try to work on this on the first occasion!

P.S. Just don't understand why Microsoft doesn't write a better documentation and official support for dynamic 👎 !

@pgolinski
Copy link

Have you tried something lik this: http://stackoverflow.com/questions/3562088/c-sharp-4-dynamic-in-expression-trees ?
I'm new to Linq Expressions, but basing on this SO answer, I have added modyficaiton in Parser class:

        Expression GenerateAdd(Expression left, Expression right)
        {
            if (left.Type == typeof(string) && right.Type == typeof(string))
            {
                return GenerateStaticMethodCall("Concat", left, right);
            }
            if (left.Type == typeof (object) || right.Type == typeof (object))
                return GenerateDynamicBinary(ExpressionType.Add, left, right);
            return Expression.Add(left, right);
        }

        Expression GenerateDynamicBinary(ExpressionType exprType, Expression left, Expression right)
        {
            var binder = Microsoft.CSharp.RuntimeBinder.Binder.BinaryOperation(
                CSharpBinderFlags.None, exprType, GetType(),
                new[]
                {
                    CSharpArgumentInfo.Create(CSharpArgumentInfoFlags.None, null),
                    CSharpArgumentInfo.Create(CSharpArgumentInfoFlags.None, null)
                });
            return Expression.Dynamic(binder, typeof(object), left, right);
        }

I'm not sure if it is right solution, but this test code works now:

            dynamic a = 5;
            dynamic b = 6;
            dynamic obj = new { a, b };

            var target = new Interpreter();
            target.SetVariable("obj", obj);
            Assert.AreEqual(a + b, target.Eval("obj.a + obj.b"));

@davideicardi
Copy link
Member Author

Thanks @pgolinski for your code.
I have already see that it is possible to use a similar code, but I don't like it very much because of the use of class Microsoft.CSharp.RuntimeBinder.Binder, that is marked as

This API supports the .NET Framework infrastructure and is not intended to be used directly from your code.

https://msdn.microsoft.com/en-us/library/microsoft.csharp.runtimebinder.binder%28v=vs.110%29.aspx

Anyway probably it is the only easy solution...the any other solution that I think of is to create a custom Binder class.

Can I ask you to send a pull request, so I can easily merge it.

thanks!

@pgolinski
Copy link

The code I pasted is all I have changed, so a pull request made from it would be quite useless.
We decided to make a use of Roslyn scripting api instead of Linq Expression parsing.
http://www.daveaglick.com/posts/compiler-platform-scripting

@mwpowellhtx
Copy link

👍 I don't like it either, but this might your best bet. That or risk venturing into the uncharted waters of Roslyn circa VS2015 CTP.

@davideicardi
Copy link
Member Author

Thanks to all! I'm just releasing version 1.3.3 with partial dynamic support (get properties and method invocation).
Special thanks to @kendarorg and @RupertAvery

@davideicardi davideicardi modified the milestones: 1.3.3, 1.5 Sep 24, 2016
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

4 participants