Forums

C# casting required to invoke Contains()

General discussion about Cobra. Releases and general news will also be posted here.
Feel free to ask questions or just say "Hello".

C# casting required to invoke Contains()

Postby Charles » Sat Sep 22, 2012 7:42 pm

At viewtopic.php?f=4&t=987&start=10#p5105 it came up that a cast was needed in the Cobra code:

# .
def contains(kvp as KeyValuePair<of TKey, TValue>) as bool
return (_forward to IDictionary<of TKey, TValue>).contains(kvp)

Without it, the error is:

BiDictionary.cobra(131): error: Type "System.Collections.Generic.Dictionary<TKey,TValue>" does not contain a definition for "Contains" and no extension method "Contains" of type "System.Collections.Generic.Dictionary<TKey,TValue>" could be found (are you missing a using directive or an assembly reference?) (C#)

This is actually a C# compilation error which I have reduced down to this:

Code: Select all
using System;
using System.Collections.Generic;

public class BiDictionary<TKey, TValue> : Object {

   protected Dictionary<TKey, TValue> _forward = null;

   public BiDictionary() {
      _forward = new Dictionary<TKey, TValue>();
   }

   public bool Contains(KeyValuePair<TKey, TValue> kvp) {
//      return ((IDictionary<TKey, TValue>)_forward).Contains(kvp); // works
//      return ((Dictionary<TKey, TValue>)_forward).Contains(kvp); // does not work
      return _forward.Contains(kvp); // does not work -- why?
   }

   public bool ContainsKey(TKey key) {
      return _forward.ContainsKey(key);  // works
   }

}

public class Program : System.Object {
   public static void Main() {
      Console.WriteLine("done.");
   }
}


Both Mono C# and Microsoft C# complain about this line:
return _forward.Contains(kvp); // does not work -- why?

Stating that there is no .Contains method. Typecasting to IDictionary<of TKey, TValue> resolves the problem (whether in C# or in the original Cobra code), though this seems silly since Dictionary<of TKey, TValue> is known at compile time to implement that interface.

@jaegs, how did you even guess to provide the type cast to IDictionary to resolve this?

When I googled for:
C# dictionary "does not contain a definition for" contains

I actually got a Cobra hit:
http://cobra-language.com/trac/cobra/changeset/1405

So apparently I have run into this before although for somewhat different classes (the nested KeyCollection and ValueCollection classes of Dictionary<of TKey, TValue>). But still don't know why.

Getting back to the situation at hand, I checked at run-time with reflection if the method Contains exists on the dictionary:
use System.Reflection

class P

def main
t = Dictionary<of String, int>
flags = BindingFlags(Public, FlattenHierarchy, Instance)
for i, methodInfo in t.getMethods(flags).toList.numbered
if 'Contains' in methodInfo.name
trace i, methodInfo


It does not as these are the only hits for methods with "Contains" in them:
Code: Select all
    trace : i=6
          - methodInfo=Boolean ContainsKey(System.String) (MonoGenericMethod)
          - at contains-reflection.cobra:11
          - in P.main

    trace : i=7
          - methodInfo=Boolean ContainsValue(Int32) (MonoGenericMethod)
          - at contains-reflection.cobra:11
          - in P.main


Both Mono 2.10 and Linux and .NET 4 on Windows are in agreement on this.

But I am expecting Contains to be there because the docs say that Dictionary<> implements ICollection<>:
http://msdn.microsoft.com/en-us/library/xfhwa508.aspx

And ICollection has a Contains method including .NET 2 and up:
http://msdn.microsoft.com/en-us/library ... 6(v=vs.100).aspx

I then added NonPublic to the binding flags and suddenly it appears:
Code: Select all
    trace : i=10
          - methodInfo=Boolean System.Collections.Generic.ICollection<System.Collections.Generic.KeyValuePair<TKey,TValu
e>>.Contains(System.Collections.Generic.KeyValuePair`2[System.String,System.Int32]) (RuntimeMethodInfo)
          - at contains-reflection.cobra:22
          - in P.main

    trace : i=13
          - methodInfo=Boolean ContainsKey(System.String) (RuntimeMethodInfo)
          - at contains-reflection.cobra:22
          - in P.main

    trace : i=14
          - methodInfo=Boolean ContainsValue(Int32) (RuntimeMethodInfo)
          - at contains-reflection.cobra:22
          - in P.main

    trace : i=39
          - methodInfo=Boolean System.Collections.IDictionary.Contains(System.Object) (RuntimeMethodInfo)
          - at contains-reflection.cobra:22
          - in P.main

I now recall that in C# that if you explicitly implement an interface member then putting "public" on it will yield an error. This following C# docs cover explicit implementation:

http://msdn.microsoft.com/en-us/library/ms173157.aspx

Note the part where they say "Both method implementations are separate, and neither is available directly on the class." (emphasis mine)

I guess I find it confusing that a .NET class might be declared as "Foo implements IBar" but any methods of IBar that were explicitly implemented will not be available through "foo.someMethod" and will require a cast. But maybe that's a requirement for the case they bring up.

But it's even more odd that the implementation of IDictionary<> does this for Contains. Based on what I see from reflection, and keeping in mind method overloading, there was no reason for the explicit implementation of Contains.

But there it is. So in the future, if a method cannot be found at compile-time, try typecasting to one of the interfaces it implements. @jaegs already knew this.

I'm not sure what, if anything, Cobra should do to help in this case:

One idea is that if the method can be bound unambiguously, as in this case, Cobra could insert the cast for you. Though I don't know if this situation comes up often enough to justify the work involved.

Another idea is that Cobra should detect that the method won't be accessible as is (currently it thinks it's fine and passes the code through to C#) and give a more helpful error message. Neither C# compiler I tested gave any hint Contains was reachable by casting to one of the known interfaces of the object.

Whew. Was this post long enough for you? :)
Charles
 
Posts: 2515
Location: Los Angeles, CA

Re: C# casting required to invoke Contains()

Postby jaegs » Sat Sep 22, 2012 8:39 pm

That's really interesting. I had not actually heard of explicit interface implementation before and really had just guessed what I needed to do in order to compile the Cobra code. I was focusing on making the class work and apparently didn't wonder why I needed to do that.

Looking at the MSDN docs for Dictionary - http://msdn.microsoft.com/en-us/library/xfhwa508.aspx there are two fairly similar Contains methods.
The first is
Code: Select all
bool ICollection<KeyValuePair<TKey,TValue>>.Contains(KeyValuePair<TKey,TValue> keyValuePair)
and the other is
Code: Select all
bool IDictionary.Contains(Object key)
jaegs
 
Posts: 58

Re: C# casting required to invoke Contains()

Postby Charles » Sat Sep 22, 2012 10:17 pm

Correct on there being two, but method overloading is enough to distinguish them AFAICT.
Charles
 
Posts: 2515
Location: Los Angeles, CA

Re: C# casting required to invoke Contains()

Postby nerdzero » Sun Sep 23, 2012 11:45 am

This sounded interesting and I remember running into changeset 1405 when I was looking at the code for the 'in' expression. I did a little more research to satisfy my curiosity and this does in fact seem to be due to explicit implementation of the various interfaces. Take a look at this code:

Code: Select all
using System;

namespace ImplementTest
{
   interface IFoo {
      string getName();
   }
   
   interface IBar {
      string getName();
   }
   
   class FooBar : IFoo, IBar {
      
      //default implementation
      public string getName() {
         return "FooBar";
      }
      
      //explicit implementation of IFoo
      string IFoo.getName() {
         return "Foo";
      }
      
      //explicit implementation of IBar
      string IBar.getName() {
         return "Bar";
      }      
   }
   
   class MainClass
   {
      public static void Main (string[] args)
      {
         FooBar fb = new FooBar();
         
         Console.WriteLine(fb.getName());
         Console.WriteLine(((IFoo)fb).getName());
         Console.WriteLine(((IBar)fb).getName());
      }
   }
}


This will output the following:
Code: Select all
FooBar
Foo
Bar


If you comment out the default implementation, you'll get this error when compiling:

Error CS1061: Type `ImplementTest.FooBar' does not contain a definition for `getName' and no extension method `getName' of type `ImplementTest.FooBar' could be found (are you missing a using directive or an assembly reference?)

Why? It doesn't know which getName to call. I think the Dictionary class runs into the same problem as it implements a number of interfaces that have a 'Contains' method. I believe the 'Contains' method on the ICollection interface would expect a KeyValuePair and the 'Contains' method of an IDictionary would expect just a Key so they must be explicitly implemented by a class that implements both. A method overload in this case wouldn't work because you wouldn't be able to tell if the KeyValuePair was actually a key and value, or the key itself. Hmm....does that sound right? Makes my brain hurt.
nerdzero
 
Posts: 286
Location: Chicago, IL

Re: C# casting required to invoke Contains()

Postby jaegs » Sun Sep 23, 2012 12:24 pm

Yes, I agree.
jaegs
 
Posts: 58

Re: C# casting required to invoke Contains()

Postby Charles » Sun Sep 23, 2012 12:49 pm

@nerdzero, I follow and agree with all of your post except the last part. C# and VB both have method overloading on arguments. So given two methods of the same name:
def contains(kvp as KeyValuePair<of TKey, TValue>) as bool
def contains(obj as Object) as bool

The first should always be invoked for any instance of KeyValuePair and the second invoked in all other cases.
Charles
 
Posts: 2515
Location: Los Angeles, CA

Re: C# casting required to invoke Contains()

Postby nerdzero » Sun Sep 23, 2012 3:05 pm

The Dictionary class also implements IReadOnlyCollection and IReadOnlyDictionary both of which also have those same signatures for Contains, right? Could that be the reason?
nerdzero
 
Posts: 286
Location: Chicago, IL

Re: C# casting required to invoke Contains()

Postby Charles » Sun Sep 23, 2012 3:45 pm

I would have to say no because

(a) they ultimately have the same sig and the implementation would be the same (the reason for separate implementations is for when you need different semantics) and

(b) my reflection code did not find it separate implementations for other interfaces

Although I find that last fact to be weird.

I guess the ultimate test would be to take the Mono source code and see if Contains can be exposed as a normal method though I don't have time for that right now (heh, or maybe ever).
Charles
 
Posts: 2515
Location: Los Angeles, CA

Re: C# casting required to invoke Contains()

Postby jaegs » Wed Sep 26, 2012 8:17 am

I was thinking more on the lines of humans understanding these methods as opposed to the compiler. As mentioned, one of the .contains looks up keys and the other looks up key-value pairs. It's not a good idea to use an overloaded method to do different tasks. In order to write clear code, one should use containsKey to look up keys.
jaegs
 
Posts: 58

Re: C# casting required to invoke Contains()

Postby nerdzero » Wed Sep 26, 2012 4:51 pm

What do you think of this code? I've heard it's bad practice to inherit from the built-in Dictionary and List types but haven't heard a good reason why not to do it yet. Anyways, this seems to work.

use System.Collections

class Dict<of TKey, TValue> inherits Dictionary<of TKey, TValue>

test
t = Dict<of String, int>()
t["key"] = 1
assert t.contains("key")
assert t.contains("key", 1)
assert not t.contains(1)
assert not t.contains("key", 2)

def contains(key as TKey, val as TValue) as bool
return .contains(KeyValuePair<of TKey, TValue>(key, val))

def contains(kvp as KeyValuePair<of TKey, TValue>) as bool
c as ICollection<of KeyValuePair<of TKey, TValue>> = this
return c.contains(kvp)

def contains(key as Object) as bool
d as IDictionary = this
return d.contains(key)

class DictExample

def main
testDict = Dict<of String, int>()
testDict["foo"] = 1_000
testDict["bar"] = 2_000

for key, val in testDict
assert testDict.contains(key) # Object
assert not testDict.contains(val) # Object
assert testDict.contains(key, val) # KeyValuePair

# not surprising...or is it?
kvp as Object = KeyValuePair<of String, int>("foo", 1_000)
assert not testDict.contains(kvp) # Object

# debatable
hmm = Dict<of String, String>()
hmm["confusing?"] = "misleading?"
hmm["misleading?"] = "confusing?"

assert hmm.contains("confusing?")
assert hmm.contains("misleading?")
assert hmm.contains("confusing?", "misleading?")
assert hmm.contains("misleading?", "confusing?")
nerdzero
 
Posts: 286
Location: Chicago, IL

Next

Return to Discussion

Who is online

Users browsing this forum: No registered users and 33 guests

cron