Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP

Loading…

dynamic support? #35

Closed
wants to merge 1 commit into from

4 participants

Keith Dahlby Tomasz Janczuk Jay Tuley Bruno Baia
Keith Dahlby

What if one could do this?

public async Task<object> Invoke(dynamic input)
{
    int anInteger = input.anInteger;
    double aNumber = input.aNumber;
    string aString = input.aString;
    bool aBoolean = input.aBoolean;
    byte[] aBuffer = input.aBuffer;
    object[] anArray = input.anArray;
    dynamic anObject = input.anObject;

    return null;
}

Or this:

public async Task<object> Invoke(dynamic input)
{
    var twoNumbers = new { a = 2, b = 3 };
    int addResult = await input.add(twoNumbers);
    return addResult * 2;
}

I have no idea if dynamic and await will even play nicely together, particularly in the second example, but this seems like an ideal place to let the DLR go to town.

Is anyone working on this? Has it been tried?

Tomasz Janczuk
Owner

@glennblock and I talked about this a bit. It is certainly a very usable programming idiom especially for someone coming from node.js. I like the idea of enabling such programming experience for C# developers as an option. Do you want to spike on this?

Keith Dahlby

Do you want to spike on this?

I'm interested, but I've never worked with managed C++...would need to get up to speed.

#3 mentions the possibility of sharing a C# library with a mono implementation - if that's likely to happen, I'd think the dynamic implementation would belong there?

Keith Dahlby

So it turns out supporting dynamic can be as trivial as swapping in an ExpandoObject behind the scenes. You can still cast as IDictionary<string, object> and get identical behavior, but you get natural dynamic get and function calls for free.

The big question, which I haven't tried to answer, is how this impacts performance.

Tomasz Janczuk
Owner

This looks very promising, thank you for looking into it. Let me run some benchmarks to see what the impact is. If there is impact perhaps we can make this opt-in using an environment variable?

@joncham Do you see any issues supporting ExpandoObject on Mono?

Jay Tuley

You can always do your own DynamicObject,IDictionary wrapping a Dictionary, that would have near identical performance to a Dictionary.

Although ExpandoObject's in general are slower at assignment of properties, but faster at getting, so I would think sticking with the Expando will likely be reasonable.

Tomasz Janczuk
Owner

This is beautiful. I measured and there is no perf impact. I also made some extra changes to the edge-cs C# compiler to streamline the async lambda experience for dynamics. One can now write code like this:

        var func = edge.func({
            source: function () {/* 
                async (dynamic input) => 
                {
                    return input.nested.text + " works";
                }
            */}
        });

        func({ nested: { text: 'Dynamic' } }, function (error, result) {
            //...
        });
    });

Merging soon, some fit & finish left.

Tomasz Janczuk tjanczuk closed this pull request from a commit
Keith Dahlby dahlbyk Support dynamic via ExpandoObject
Closes #35
b67d02d
Tomasz Janczuk tjanczuk closed this in b67d02d
Tomasz Janczuk
Owner

This is now in NPM as edge@0.7.11. Thanks!

Bruno Baia

Using dynamics from nodejs to .NET works, but .NET to nodejs fails:

var edge = require('edge');

var helloWorld = edge.func('async (input) => { dynamic expando = new System.Dynamic.ExpandoObject(); expando.one = "value"; expando.two = 1; return expando; }');

helloWorld('JavaScript', function (error, result) {
    if (error) throw error;
    console.log(result);
});

logs

[ { Key: 'one', Value: 'value' }, { Key: 'two', Value: 1 } ]
Keith Dahlby

What do you expect?

Jay Tuley

If I were not deeply familiar with dlr workings. I would expect the expando to be returned as

 { one: "value" , Two: 1 } 
Bruno Baia

Exactly Jay!

Maybe a 'is'/'IsAssignableFrom' issue.

Jay Tuley

Thinking about it more, it seems like any IDictionary<string,object> should be returned as prototype object rather than a list of key pairs. Maybe not a dlr specific issue of edgejs?

Keith Dahlby

Thinking about it more, it seems like any IDictionary<string,object> should be returned as prototype object rather than a list of key pairs. Maybe not a dlr specific issue of edgejs?

This seems reasonable to me - perhaps create a separate issue to track that proposal?

Jay Tuley

an IDictionary is currently marshaled but an IDictionary<,> is not -> issue #68

Bruno Baia bbaia referenced this pull request from a commit
Bruno Baia bbaia Fix #35 & #68: dynamic support e6b4ed0
Bruno Baia bbaia referenced this pull request from a commit
Bruno Baia bbaia Fix #35 & #68: dynamic support eba1c6f
Tomasz Janczuk tjanczuk referenced this pull request from a commit
Bruno Baia bbaia Fix #35 & #68: dynamic support dd4e689
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Commits on May 26, 2013
  1. Keith Dahlby

    Support dynamic via ExpandoObject

    dahlbyk authored
    Closes #35
This page is out of date. Refresh to see the latest.
Showing with 55 additions and 25 deletions.
  1. +3 −1 src/clrfunc.cpp
  2. +52 −24 test/tests.cs
4 src/clrfunc.cpp
View
@@ -1,5 +1,7 @@
#include "edge.h"
+#using <System.Core.dll>
+
ClrFunc::ClrFunc()
{
// empty
@@ -304,7 +306,7 @@ System::Object^ ClrFunc::MarshalV8ToCLR(Handle<v8::Value> jsdata)
}
else if (jsdata->IsObject())
{
- Dictionary<System::String^,System::Object^>^ netobject = gcnew Dictionary<System::String^,System::Object^>();
+ IDictionary<System::String^,System::Object^>^ netobject = gcnew System::Dynamic::ExpandoObject();
Handle<v8::Object> jsobject = Handle<v8::Object>::Cast(jsdata);
Handle<v8::Array> propertyNames = jsobject->GetPropertyNames();
for (unsigned int i = 0; i < propertyNames->Length(); i++)
76 test/tests.cs
View
@@ -25,10 +25,10 @@ namespace Edge.Tests
{
public class Startup
{
- string ValidateMarshalNodeJsToNet(object input, bool expectFunction)
+ string ValidateMarshalNodeJsToNet(dynamic input, bool expectFunction)
{
string result = "yes";
- IDictionary<string, object> data = input as IDictionary<string, object>;
+ var data = (IDictionary<string, object>)input;
try
{
int a = (int)data["a"];
@@ -61,21 +61,53 @@ string ValidateMarshalNodeJsToNet(object input, bool expectFunction)
result = e.ToString();
}
+ try
+ {
+ int a = input.a;
+ if (a != 1) throw new Exception("dynamic a is not 1");
+ double b = input.b;
+ if (b != 3.1415) throw new Exception("dynamic b is not 3.1415");
+ string c = input.c;
+ if (c != "foo") throw new Exception("dynamic c is not foo");
+ bool d = input.d;
+ if (d != true) throw new Exception("dynamic d is not true");
+ bool e = input.e;
+ if (e != false) throw new Exception("dynamic e is not false");
+ byte[] f = input.f;
+ if (f.Length != 10) throw new Exception("dynamic f.length is not 10");
+ dynamic[] g = input.g;
+ if (g.Length != 2) throw new Exception("dynamic g.length is not 2");
+ if ((int)g[0] != 1) throw new Exception("dynamic g[0] is not 1");
+ if ((string)g[1] != "foo") throw new Exception("dynamic g[1] is not foo");
+ dynamic h = input.h;
+ if ((string)h.a != "foo") throw new Exception("dynamic h.a is not foo");
+ if ((int)h.b != 12) throw new Exception("dynamic h.b is not 12");
+ if (expectFunction)
+ {
+ var i = input.i as Func<object, Task<object>>;
+ if (i == null) throw new Exception("dynamic i is not a Func<object,Task<object>>");
+ }
+ }
+ catch (Exception e)
+ {
+ result = e.ToString();
+ }
+
return result;
}
- public Task<object> Invoke(object input)
+ public Task<object> Invoke(dynamic input)
{
- return Task.FromResult<object>(".NET welcomes " + input.ToString());
+ return Task.FromResult<object>(".NET welcomes " + input);
}
- public Task<object> MarshalIn(object input)
+ public Task<object> MarshalIn(dynamic input)
{
string result = ValidateMarshalNodeJsToNet(input, true);
return Task.FromResult<object>(result);
}
- public Task<object> MarshalBack(object input)
+ public Task<object> MarshalBack(dynamic input)
{
var result = new {
a = 1,
@@ -92,12 +124,12 @@ public Task<object> MarshalBack(object input)
return Task.FromResult<object>(result);
}
- public Task<object> NetExceptionTaskStart(object input)
+ public Task<object> NetExceptionTaskStart(dynamic input)
{
throw new Exception("Test .NET exception");
}
- public Task<object> NetExceptionCLRThread(object input)
+ public Task<object> NetExceptionCLRThread(dynamic input)
{
Task<object> task = new Task<object>(() =>
{
@@ -109,16 +141,14 @@ public Task<object> NetExceptionCLRThread(object input)
return task;
}
- public async Task<object> InvokeBack(object input)
+ public async Task<object> InvokeBack(dynamic input)
{
- Func<object, Task<object>> hello = (Func<object, Task<object>>)(((IDictionary<string, object>)input)["hello"]);
- var result = await hello(".NET");
+ var result = await input.hello(".NET");
return result;
}
- public async Task<object> MarshalInFromNet(object input)
+ public async Task<object> MarshalInFromNet(dynamic input)
{
- Func<object, Task<object>> hello = (Func<object, Task<object>>)(((IDictionary<string, object>)input)["hello"]);
var payload = new {
a = 1,
b = 3.1415,
@@ -130,25 +160,23 @@ public Task<object> NetExceptionCLRThread(object input)
h = new { a = "foo", b = 12 },
i = (Func<object,Task<object>>)(async (i) => { return i; })
};
- var result = await hello(payload);
+ var result = await input.hello(payload);
return result;
}
- public async Task<object> MarshalBackToNet(object input)
+ public async Task<object> MarshalBackToNet(dynamic input)
{
- Func<object, Task<object>> hello = (Func<object, Task<object>>)(((IDictionary<string, object>)input)["hello"]);
- var payload = await hello(null);
+ var payload = await input.hello(null);
string result = ValidateMarshalNodeJsToNet(payload, false);
return result;
}
- public async Task<object> MarshalException(object input)
+ public async Task<object> MarshalException(dynamic input)
{
- string result = "No exception was thrown";
- Func<object, Task<object>> hello = (Func<object, Task<object>>)(((IDictionary<string, object>)input)["hello"]);
+ string result = "No exception was thrown";
try
{
- await hello(null);
+ await input.hello(null);
}
catch (Exception e)
{
@@ -159,11 +187,11 @@ public Task<object> NetExceptionCLRThread(object input)
}
private WeakReference weakRefToNodejsFunc;
- public Task<object> InvokeBackAfterCLRCallHasFinished(object input)
+ public Task<object> InvokeBackAfterCLRCallHasFinished(dynamic input)
{
var trace = new List<string>();
trace.Add("InvokeBackAfterCLRCallHasFinished#EnteredCLR");
- Func<object, Task<object>> callback = (Func<object, Task<object>>)(((IDictionary<string, object>)input)["eventCallback"]);
+ Func<object, Task<object>> callback = input.eventCallback;
// The target of the callback function is the ref to the NodejsFunc acting as
// the proxy for the JS function to call.
@@ -186,7 +214,7 @@ public Task<object> InvokeBackAfterCLRCallHasFinished(object input)
return result.Task;
}
- public Task<object> EnsureNodejsFuncIsCollected(object input) {
+ public Task<object> EnsureNodejsFuncIsCollected(dynamic input) {
var succeed = false;
GC.Collect();
Something went wrong with that request. Please try again.