ClearScript icon indicating copy to clipboard operation
ClearScript copied to clipboard

How to map obj[Symbol.dispose] to obj.Dispose()

Open DavidBal opened this issue 2 years ago • 3 comments

Hi ClearScript Team,

in the recent update of TypeScript the using statement was added. https://devblogs.microsoft.com/typescript/announcing-typescript-5-2/#using-declarations-and-explicit-resource-management

The JavaScript for this feature looks something like this:

const obj = new DispoableObject();

try{

}
finally
{
 obj[Symbol.dispose]();
}

Is there any way we can map this call to CSharp?

Thanks in advance.

Best regards, David

DavidBal avatar Aug 29 '23 10:08 DavidBal

Hi @DavidBal,

Host objects in ClearScript currently don't support Symbol.dispose; in fact, V8 defines no such symbol. However, a relevant proposal appears to be in flight, and TypeScript is evidently ahead of the curve.

Anyway, assuming that Symbol.dispose is available, you could do something like this:

dynamic setupFunc = engine.Evaluate(@"(
    function (host, IDisposable) {
        Object.defineProperty(Object.getPrototypeOf(host), Symbol.dispose, {
            get() {
                const disposable = host.asType(IDisposable, this);
                return disposable ? () => disposable.Dispose() : undefined;
            }
        });
    }
)");
setupFunc(new HostFunctions(), typeof(IDisposable).ToHostType(engine));

With this setup in place, most host objects will support Symbol.dispose. We say "most" because the code above adds Symbol.dispose only to the HostFunctions prototype, which is shared among all "normal" host objects. There are other prototypes specifically for delegates and dynamic objects, for which a similar technique can be used if necessary.

We'll keep an eye on the proposal and add full support if and when it's incorporated into the JavaScript standard.

Good luck!

ClearScriptLib avatar Aug 29 '23 14:08 ClearScriptLib

Hi again,

Here's a more complete solution:

dynamic setupFunc = engine.Evaluate(@"(
    function (obj, host, IDisposable) {
        Object.defineProperty(Object.getPrototypeOf(obj), Symbol.dispose, {
            get() {
                const disposable = host.asType(IDisposable, this);
                return disposable ? () => disposable.Dispose() : undefined;
            }
        });
    }
)");
var host = new HostFunctions();
var disposable = typeof(IDisposable).ToHostType(engine);
setupFunc(host, host, disposable);
setupFunc(new Action(() => {}), host, disposable);
setupFunc(new PropertyBag(), host, disposable);

This should set up Symbol.dispose support for all host objects and types.

Cheers!

ClearScriptLib avatar Aug 29 '23 15:08 ClearScriptLib

Hi,

thank you that works great.

Here is my full implementation, should someone else have the same problem:

dynamic setupDisposeFunc = scriptEngine.Evaluate("""
(
    function (obj, host, IDisposable) {
                            
        Symbol.dispose ??= Symbol("Symbol.dispose");
        
        Object.defineProperty(Object.getPrototypeOf(obj), Symbol.dispose, {
            get() {
                const disposable = host.asType(IDisposable, this);
                return disposable ? () => disposable.Dispose() : undefined;
            }
        });
    }
)
""");

dynamic setupAsyncDisposeFunc = scriptEngine.Evaluate("""
(
    function (obj, host, IAsyncDisposable) {
                            
        Symbol.asyncDispose ??= Symbol("Symbol.asyncDispose");
        
        Object.defineProperty(Object.getPrototypeOf(obj), Symbol.asyncDispose, {
            get() {
                const disposable = host.asType(IAsyncDisposable, this);
                return disposable ? () => disposable.DisposeAsync() : undefined;
            }
        });
    }
)
""");

HostFunctions host = new HostFunctions();
object disposable = typeof(IDisposable).ToHostType(scriptEngine);
object asyncDisposable = typeof(IAsyncDisposable).ToHostType(scriptEngine);

setupDisposeFunc(host, host, disposable);
setupDisposeFunc(new Action(() => { }), host, disposable);
setupDisposeFunc(new PropertyBag(), host, disposable);

setupAsyncDisposeFunc(host, host, asyncDisposable);
setupAsyncDisposeFunc(new Action(() => { }), host, asyncDisposable);
setupAsyncDisposeFunc(new PropertyBag(), host, asyncDisposable);

Best regards, David

DavidBal avatar Aug 30 '23 06:08 DavidBal