Skip to content
This repository has been archived by the owner on Mar 9, 2021. It is now read-only.

How to correctly implement IDisposable on imported types? #159

Closed
n9 opened this issue Mar 22, 2013 · 11 comments
Closed

How to correctly implement IDisposable on imported types? #159

n9 opened this issue Mar 22, 2013 · 11 comments

Comments

@n9
Copy link

n9 commented Mar 22, 2013

I have the following code:

    [Imported]
    [IgnoreNamespace]
    [ModuleName("sqlite3")]
    [ScriptName("Database")]
    public class SQLite3Database : IDisposable
    {
        public delegate void OpenCallback(string error);

        public SQLite3Database(string filename, int mode = 0, OpenCallback callback = null) { }

        public void Close() { }

        [InlineCode("{this}.close()")]
        void IDisposable.Dispose() { }
    }

And I am using it like this:

    using (var db = new SQLite3Database(@"c:\Temp\temp.db"))
    {

    }

But it produces the following JavaScript:

    var db = new sqlite3.Database('c:\\Temp\\temp.db', 0, null);
    try {
    }
    finally {
        if (ss.isValue(db)) {
            db.dispose();
        }
    }

I would like to produce db.close() instead of db.dispose(). How to achieve that?

@txdv
Copy link

txdv commented Mar 22, 2013

Don't produce close instead of dispose, just put the Close into the Dispose!

    [Imported]
    [IgnoreNamespace]
    [ModuleName("sqlite3")]
    [ScriptName("Database")]
    public class SQLite3Database : IDisposable
    {
        public delegate void OpenCallback(string error);

        public SQLite3Database(string filename, int mode = 0, OpenCallback callback = null) { }

        [InlineCode("{this}.close()")]
        public void Close() { }

        void IDisposable.Dispose()
        {
            Close();
        }
    }

@n9
Copy link
Author

n9 commented Mar 22, 2013

Does not work for me. What code it produces for you?

@erik-kallen
Copy link
Contributor

The problem is that someone might do

void DoDispose(IDisposable d) {
    d.Dispose();
}

void DoSomething() {
    var db = new SQLite3Database();
    DoDispose(db);
}

Since DoDispose only operates on the IDisposable, it has no idea that it should call the close method instead of dispose.

I do agree, however, that this is an issue that should be solved in some way, if possible. Any suggestions?

@txdv
Copy link

txdv commented Mar 23, 2013

this

using System;
using System.Runtime.CompilerServices;

[Imported]
[IgnoreNamespace]
[ModuleName("sqlite3")]
[ScriptName("Database")]
public class SQLite3Database : IDisposable
{
    public delegate void OpenCallback(string error);

    public SQLite3Database(string filename, int mode = 0, OpenCallback callback = null) { }

    [InlineCode("{this}.close()")]
    public void Close() { }

    void IDisposable.Dispose()
    {
        Close();
    }
}

public class MainApp
{
    [InlineCode("console.log({o})")]
    static void Log(object o)
    {
    }

    public static void Main()
    {
        using (var db = new SQLite3Database(@"c:\Temp\temp.db"))
        {
            Log("ASD");
        }
    }
}

gets compiled to

require('mscorlib');
var sqlite3 = require('sqlite3');
////////////////////////////////////////////////////////////////////////////////
// MainApp
var $MainApp = function() {
};
$MainApp.main = function() {
    var db = new sqlite3.Database('c:\\Temp\\temp.db', 0, null);
    try {
        console.log('ASD');
    }
    finally {
        if (ss.isValue(db)) {
            db.dispose();
        }
    }
};
ss.registerClass(global, 'MainApp', $MainApp);
$MainApp.main();

It just ignores the own defined Dispose function, since it is marked as Imported(or one of the other attributes may influence this behavior) which is a bug in my opinion.
A workaround would be putting the native JS class into a wrapper class(I took the liberty to use Action instead of a defined delegate, because all cool kids do it nowadays like that):

using System;
using System.Runtime.CompilerServices;

[Imported]
[IgnoreNamespace]
[ModuleName("sqlite3")]
[ScriptName("Database")]
class JsSQLite3Database
{
    public JsSQLite3Database(string filename, int mode = 0, Action<string> callback = null) { }

    [InlineCode("{this}.close()")]
    public void Close() { }
}

public class SQLite3Database : IDisposable
{
    JsSQLite3Database db;

    public SQLite3Database(string filename, int mode = 0, Action<string> callback = null)
    {
        db = new JsSQLite3Database(filename, mode, callback);
    }

    public void Dispose()
    {
        if (db != null) {
            db.Close();
            db = null;
        }
    }
}


public class MainApp
{
    [InlineCode("console.log({o})")]
    static void Log(object o)
    {
    }

    public static void Main()
    {
        using (var db = new SQLite3Database(@"c:\Temp\temp.db"))
        {
            Log("ASD");
        }
    }
}

The result:

require('mscorlib');
var sqlite3 = require('sqlite3');
////////////////////////////////////////////////////////////////////////////////
// MainApp
var $MainApp = function() {
};
$MainApp.main = function() {
    var db = new $SQLite3Database('c:\\Temp\\temp.db', 0, null);
    try {
        console.log('ASD');
    }
    finally {
        if (ss.isValue(db)) {
            db.dispose();
        }
    }
};
////////////////////////////////////////////////////////////////////////////////
// SQLite3Database
var $SQLite3Database = function(filename, mode, callback) {
    this.$db = null;
    this.$db = new sqlite3.Database(filename, mode, callback);
};
$SQLite3Database.prototype = {
    dispose: function() {
        if (ss.isValue(this.$db)) {
            this.$db.close();
            this.$db = null;
        }
    }
};
ss.registerClass(global, 'MainApp', $MainApp);
ss.registerClass(global, 'SQLite3Database', $SQLite3Database, null, ss.IDisposable);
$MainApp.main();

The benefit is that SQLite3Database is not just some imported class anymore, but a full Saltarelle .NET citizen, which would come in handy if and when other features like reflection get added.

@n9
Copy link
Author

n9 commented Mar 23, 2013

I have tried to use subclassing:

    public class SQLite3DatabaseEx : SQLite3Database, IDisposable
    {
        public SQLite3DatabaseEx(string filename, int? mode = null, SQLite3OpenCallback callback = null)
            : base(filename, mode, callback) { }

        [InlineCode("{this}.close()")]
        void IDisposable.Dispose() { }
    }

However, it produces the following error:

Uncaught TypeError: Use the new operator to create new Database objects

The problem seems to be here:

var $SQLite3_SQLite3DatabaseEx = function(filename, mode, callback) {
    sqlite3.Database.call(this, filename, mode, callback);
};

@n9
Copy link
Author

n9 commented Mar 23, 2013

Maybe the solution is to use the new operator and pass the created object as a prototype to the SQLite3DatabaseEx.

@txdv
Copy link

txdv commented Mar 23, 2013

You can't subclass from an imported class.

@erik-kallen
Copy link
Contributor

You could do something like typeof(SQLite3Database).Prototype.dispose = typeof(SQLite3Database).Prototype.close.

Perhaps some feature should be added to allow specifying any implementation for IDisposable.Dispose(), although if this is done it will be illegal to upcast objects of the type.

@n9
Copy link
Author

n9 commented Mar 23, 2013

Yes, that's how I am currently doing it:

        [InlineCode("{$SQLite3.SQLite3Database}.prototype.dispose = {$SQLite3.SQLite3Database}.prototype.close")]
        static void AddDisposeToDatabase() { }

        [InlineCode("{$SQLite3.SQLite3Statement}.prototype.dispose = {$SQLite3.SQLite3Statement}.prototype.finalize")]
        static void AddDisposeToStatement() { }

Why do not allow to subclass from imported class?

@txdv
Copy link

txdv commented Mar 23, 2013

Overloading(especially constructor overloading) and other functionality is handled by registering classes and adhering to specially crafted javascript. Not all imported libraries will conform to this.

The shorter answer is: because imported classes won't be registered with ss.registerClass.

@erik-kallen
Copy link
Contributor

See #405

Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Projects
None yet
Development

No branches or pull requests

3 participants