This project has moved. For the latest updates, please go here.

V8 Iterators

Nov 12, 2015 at 9:40 PM
It would be great if .NET objects exposed to V8 could provide ECMA Script 6 Iterator functionality.

In order to achieve this, they would have to have a Symbol.iterator property when seen from V8.

I would like to patch ClearScript in order to achieve this. Approach: Introduce some attribute (e.g. V8Iterator) to indicate which method of the .NET class should become the Symbol.iterator property. When the V8 object is built, the property is added.

Now I looked at the ClearScript sources, but I had to realize that I do not understand the mechanism how the V8 object is built.

Where exactly is the reflection on the .NET object done?
Coordinator
Nov 13, 2015 at 1:51 AM
Edited Nov 13, 2015 at 2:19 AM
Hello JohnGeeB,

Patching ClearScript may not be necessary. Consider this code:
engine.Script.host = new HostFunctions();
engine.AddHostType(typeof(IEnumerable));
engine.Execute(@"
    host.constructor.prototype[Symbol.iterator] = function () {
        var enumerable = host.asType(IEnumerable, this);
        if (host.isNull(enumerable))
            throw new TypeError('the host object is not enumerable');
        var enumerator = enumerable.GetEnumerator();
        return {
            next: function () {
                if (enumerator.MoveNext())
                    return { done: false, value: enumerator.Current };
                return { done: true };
            }
        };
    }
");
With this setup, all exposed .NET objects that implement IEnumerable support the ES6 iteration protocols.

Good luck!
Nov 13, 2015 at 10:20 PM
Edited Nov 13, 2015 at 10:23 PM
Amazing. Works perfectly. Thank you.

May I ask another -- related -- question? The goal is Linq-style iterating, e.g. implementing a forEach with a callback parameter.

Here http://www.csharpcity.com/2013/from-c-to-javascript-and-back-again-via-clearscript/ it is explained how to pass a JS callback to a .NET function requiring an Action parameter (some code added by myself and tested, it works):
C#:

public class MyNetCollection {
   public void forEach (Action<object> a) { ...}
}
...
engine.AddHostType("Action", typeof(Action<object>));
...

JS:

// we want to sum up all values in myNetCollection
var s = 0;
myNetCollection.forEach ( new Action( function(v) {
   s += v; // &#43; should be a plus
} ));
Now that wrapping of the callback function into the new Action ( ) constructor is inconvenient. Is there some clean way of doing that on the .NET side? I.e. such that forEach accepts a parameter of type object and does the wrapping into the Action itself? I think I could abuse some method of the HostFunctions class and create a delegate, yet the methods of the class do not seem to be intended to be used from outside a script? Is there a cleaner way? Assumption: myNetCollection, of course, knows which script engine it belongs to.
Coordinator
Nov 16, 2015 at 3:08 AM
Hi again,

Amazing. Works perfectly.

Well, not quite :) It doesn't support strongly-typed enumeration via IEnumerable<T>. We'll add more robust support for ES6 iteration in a future update.

Now that wrapping of the callback function into the new Action() constructor is inconvenient. Is there some clean way of doing that on the .NET side? I.e. such that forEach accepts a parameter of type object and does the wrapping into the Action itself?

Sure. Here's a simple extension class that implements a script-friendly forEach method for .NET enumerables:
public static class ScriptEnumerable {
    public static void forEach<T>(this IEnumerable<T> source, object action) {
        source.ToList().ForEach(item => ((dynamic)action)(item));
    }
}
And here's some test code that demonstrates its usage:
engine.Script.testDictionary = new Dictionary<string, object> {
    { "foo", 123 },
    { "bar", 456.789 },
    { "baz", "hello" },
    { "qux", DayOfWeek.Wednesday }
};
engine.AddHostType(typeof(ScriptEnumerable));
engine.AddHostType(typeof(Console));
engine.Execute(@"
    testDictionary.forEach(function(item) {
        Console.WriteLine(item);
    })
");
Cheers!
Nov 21, 2015 at 10:50 AM
Edited Nov 21, 2015 at 10:52 AM
Thank you so much. Works like a charm. And seems to improve performance over the Action approach I presented above. (Did not measure performance, though.)

Remaining question: Do I have to ((IDisposable)action).Dispose (); the action object (i.e. the ScriptItem) at the end of forEach?

The ClearScript code seems to do that sometimes with objects returned from Evaluate, and it is not entirely clear to me when that is necessary.
Coordinator
Nov 23, 2015 at 1:22 AM
Hi JohnGeeB,

In general, if you're using a recent ClearScript version, you don't have to dispose V8 script objects. However, in some scenarios doing so may improve the application's memory efficiency.

The script engine and the CLR use separate garbage-collected heaps. When you pass an object from one to the other, the destination environment gets a proxy that by necessity prevents its target from being collected. Only when the proxy itself is collected does the target become available to its garbage collector.

Disposing the proxy simply speeds up that process by releasing the target immediately. This may be advisable if, for example, the target is known to be a very large object that is no longer in use.

Good luck!