polytronicgr / sharpkit

Automatically exported from code.google.com/p/sharpkit
0 stars 0 forks source link

IEqualityComparer not found #290

Open GoogleCodeExporter opened 8 years ago

GoogleCodeExporter commented 8 years ago
If you attempt to control the equality/hashcode via a custom IEqualityComparer 
it leads to an error at construction time.

    [JsType(JsMode.Clr, Filename = "res/Example.js")]
    public class Pair
    {
        public readonly int X;
        public readonly int Y;

        public Pair(int x, int y)
        {
            X = x;
            Y = y;
        }

        public override int GetHashCode()
        {
            var hc = X;
            return ((hc << 5 | hc >> 27) + Y);
        }

        public override bool Equals(object obj)
        {
            var other = (Pair) obj;
            return X == other.X && Y == other.Y;
        }
    }

    [JsType(JsMode.Clr, Filename = "res/Example.js")]
    public class PairEqualityComparer : IEqualityComparer<Pair>
    {

        public bool Equals(Pair p1, Pair p2)
        {
            return p1.X == p2.X && p1.Y == p2.Y;
        }

        public int GetHashCode(Pair p)
        {
            var hc = p.X;
            return ((hc << 5 | hc >> 27) + p.Y);
        }

    }

    [JsType(JsMode.Global, Filename = "res/Default.js")]
    public class DefaultClient
    {
        private static void DefaultClient_Load()
        {
            new jQuery(HtmlContext.document.body).append("Ready<br/>");
        }

        private static void btnTest_click(DOMEvent e)
        {
            var a = new Pair(x: 1, y: 2);
            var b = new Pair(x: 1, y: 2);
            var c = new Pair(x: 4, y: 2);

            var pairEqualityComparer = new PairEqualityComparer();

            var dic = new Dictionary<Pair, string>(pairEqualityComparer);

            dic[a] = "one";
            dic[b] = "two";
            dic[c] = "three";

            new jQuery(HtmlContext.document.body).append(string.Format("Hashcodes a={0}, b={1}, c={2}<br/>",
                                                                       a.GetHashCode(), b.GetHashCode(), c.GetHashCode()));

            new jQuery(HtmlContext.document.body).append("dic[a]: " + dic[a] + "<br/>");
            new jQuery(HtmlContext.document.body).append("dic[b]: " + dic[b] + "<br/>");
            new jQuery(HtmlContext.document.body).append("dic[c]: " + dic[c] + "<br/>");

            /*new jQuery(HtmlContext.document.body).append("Hello world<br/>");

            var items = new List<string> {"aardvark", "badger", "couger"};

            foreach (var animal in items)
            {
                new jQuery(HtmlContext.document.body).append(animal + "<br/>");
            }

            new jQuery(HtmlContext.document.body).append("Then we reverse it...<br/>");

            items.Reverse();

            foreach (var animal in items)
            {
                new jQuery(HtmlContext.document.body).append(animal + "<br/>");
            }

            new jQuery(HtmlContext.document.body).append(string.Format("number of squares:{0}<br/>", Example.Test()));*/
        }
    }

leads to an error:

JavaScript runtime error: type IEqualityComparer$1 was not found with (with 
IgnoreNamespace)

This occurs with the release version
/*Generated by SharpKit 5 v5.01.0000*/

The initialisation of the interface seems to lack the $1 suffix but there 
appear to be more issues after that.

var System$Collection$Generic$IEqualityComparer = {fullname: 
"System.Collection.Generic.IEqualityComparer", baseTypeName: "System.Object", 
assemblyName: "SharpKit.JsClr", Kind: "Interface"};

Original issue reported on code.google.com by co...@gravill.com on 22 Apr 2013 at 3:02

GoogleCodeExporter commented 8 years ago

Original comment by sebastia...@gmail.com on 1 Nov 2013 at 8:56

GoogleCodeExporter commented 8 years ago

Original comment by DanelK...@gmail.com on 3 Nov 2013 at 9:45

GoogleCodeExporter commented 8 years ago
The IEqualityComparer is present now but the C# code to get the hashcode gets 
translated into

GetHashCode$$T

When the comparer has:

GetHashCode$$Pair

This causes a runtime failure.

I found an unpleasant workaround, declare a fake T inner class and hashcode 
method that uses that.

    [JsType(JsMode.Clr, Filename = "res/Example.js")]
    public class PairEqualityComparer : IEqualityComparer<Pair>
    {
        class T
        {

        }

        public bool Equals(Pair p1, Pair p2)
        {
            return p1.X == p2.X && p1.Y == p2.Y;
        }

        public int GetHashCode(Pair p)
        {
            var hc = p.X;
            return ((hc << 5 | hc >> 27) + p.Y);
        }

        //Workaround
        int GetHashCode(T obj)
        {
            var pair = (Pair)obj)
            var hc = p.X;
            return ((hc << 5 | hc >> 27) + p.Y);
        }

    }

It would be great to fix this as it's very fragile surprising why it works! :-)

Original comment by co...@gravill.com on 20 Dec 2013 at 12:03

GoogleCodeExporter commented 8 years ago
Thanks we'll check it out.
I believe that you can just rename the method, using JsMethodAttribute as a 
less unpleasant workaround.

Original comment by DanelK...@gmail.com on 20 Dec 2013 at 12:13

GoogleCodeExporter commented 8 years ago
This is an isolated example how to reproduce it:

var obj = new GenericClass<int>();
obj.foo(0);

    public class GenericClass<TKey>
    {

        IGenericComparer<TKey> cmp;

        public void foo(TKey value)
        {
            cmp = new GenericComparer<TKey>();
            cmp.compare(value);
        }
    }

    public interface IGenericComparer<T1>
    {
        void compare(T1 value);
        void compare(object value); //provocate overload
    }

    public class GenericComparer<T2> : IGenericComparer<T2>
    {

        public void compare(T2 value)
        {
            jQueryContext.alert("success");
        }

        public void compare(object value) { } //provocate overload

    }

When you replace 
        IGenericComparer<TKey> cmp;
with:
        GenericComparer<TKey> cmp;
than no error will occur. OR when you rename T2 to T2.

Solution: an implementing class needs to dynamic link it local methods to the 
interface methods on client compilation time.

jsType.definition["ImplementationMethod$$T2"] = 
jsType.definition["InterfaceMethod$$T1"]
Hint: This is pseudocode!!! It should show, what i mean.

I've prepared such a mechanism, but it was not activated yet. The mechamism was 
implemented to archive explicit implemented interfaces (and fixing other 
interface bugs).

Original comment by sebastia...@gmail.com on 30 Dec 2013 at 6:10

GoogleCodeExporter commented 8 years ago
I wrote some more thoughts in this document:

https://docs.google.com/document/d/19XZ0n1YXA3jMgAf8TNrkIO7mGlWIN19fgeOa8r2CNxA/
edit?usp=sharing

Original comment by sebastia...@gmail.com on 31 Dec 2013 at 12:34

GoogleCodeExporter commented 8 years ago
Hi, I had a look at the document and your approach looks promising.

Personally, I'm fine with the generated methods looking "not nice" if they're 
able to safely capture more of the semantics of C#. Implementing overloading, 
and interface resolution in a language without them is always going to be a bit 
ugly. :-) Calling functions which are fully decorated with interface names and 
parameter type suffixes, while long is as you say, is clearly and predictably 
correct.

I'd actually been surprised in the past when I encountered the rule "that 
methods become only a postfix, when there’s at least one more overload" which 
is nice for brevity but is something extra to remember. Presumably after using 
a minimiser they'd be equivalent anyway?

I mainly read the JavaScript to diagnose problems and in that context verbose 
name mangling is OK. One uncertainty I have is: how intuitive are the meaning 
of $, #, and $$. I don't have an alternative though, and hopefully when people 
are reading the JavaScript for their own interfaces and classes it's more 
obvious.

The custom names aren't so vital for us as we're aiming consistency between 
algorithms that run in C# and JavaScript via SharpKit. Any kind of manual fixes 
is unfortunately a challenge to keep in sync.

Hope that helps

Original comment by co...@gravill.com on 9 Jan 2014 at 11:51

GoogleCodeExporter commented 8 years ago
Renaming the method with:

[JsMethod(Name = "GetHashCode$$T", NativeOverloads = true)]

does work and is a bit nicer.

Original comment by co...@gravill.com on 14 Jan 2014 at 10:59