tags:

views:

543

answers:

2

Given the following struct:

public struct Foo<T>
{
   public Foo(T obj) { }

   public static implicit operator Foo<T>(T input)
   {
      return new Foo<T>(input);
   }
}

This code compiles:

private Foo<ICloneable> MakeFoo()
{
    string c = "hello";
    return c; // Success: string is ICloneable, ICloneable implicitly converted to Foo<ICloneable>
}

But this code doesn't compile -- why?

private Foo<ICloneable> MakeFoo()
{
    ICloneable c = "hello";
    return c; // Error: ICloneable can't be converted to Foo<ICloneable>. WTH?
}
+16  A: 

Apparently, implicit user defined conversions don't work when one of the types is an interface. From the C# specs:


6.4.1 Permitted user-defined conversions

C# permits only certain user-defined conversions to be declared. In particular, it is not possible to redefine an already existing implicit or explicit conversion. For a given source type S and target type T, if S or T are nullable types, let S0 and T0 refer to their underlying types, otherwise S0 and T0 are equal to S and T respectively. A class or struct is permitted to declare a conversion from a source type S to a target type T only if all of the following are true:

  • S0 and T0 are different types.
  • Either S0 or T0 is the class or struct type in which the operator declaration takes place.
  • Neither S0 nor T0 is an interface-type.
  • Excluding user-defined conversions, a conversion does not exist from S to T or from T to S.


In your first method, both types are not interface types, so the user defined implicit conversion works.

The specs are not very clear, but it seems to me that if one of the types involved is an interface type, the compiler doesn't even try to look up any user-defined implicit conversions.

Philippe Leybaert
Wow. What a strange requirement. I would like to hear from Lippert or Skeet or some other C# expert why interface types won't work for this; surely there must be a good reason behind this oddity.
Judah Himango
After some experimenting, I see that interfaces do seem to be the key here. What's strange is it seems less of a mental leap for the compiler to figure out what to do often times. Hmm.
Judah Himango
I'd be very interested in an explanation of how the first sample compiles. Given the rules in section 6.4.4, I don't see how it picks the `Foo<ICloneable>` conversion given that `ICloneable` doesn't encompass `string` (since `ICloneable` is an interface) and `Foo<string>` is not encompassed by `Foo<ICloneable>` (since there is no implicit conversion from `Foo<string>` to `Foo<ICloneable>`). Maybe 6.1.9 "Implicit conversions involving type parameters" is coming into play somehow?
zinglon
Yeah, I don't fully understand it still; I'm still left wondering if this is a bug in the compiler. I've contacted Eric Lippert and asked him to chime in here, as he's a regular on StackOverflow. Maybe he can show us all the light.
Judah Himango
+11  A: 

(Following up on the comments of the accepted answer.)

Yes, this is a very, very confusing part of the spec. The whole bit about "encompassing types" in particular is deeply flawed. I've been trying for several years now to find the time to completely rewrite that whole section into something more coherent but it has never been a high enough priority.

Essentially what we've got here is a contradiction; we say that there are no user-defined implicit conversions involving interfaces, but clearly that is not true in this case; there's a user-defined implicit conversion from IC to Foo<IC>, demonstrated by the fact that a string goes to Foo<IC> via that conversion.

What we really ought to be emphasizing better is this line that you quoted:

In particular, it is not possible to redefine an already existing implicit or explicit conversion.

That's what motivates this whole thing; the desire to not allow you to ever think that you're doing a representation-preserving type test when in fact you are calling a user-defined method. Consider for example this variation:

interface IBar {}
interface IFoo : IBar {}
class Foo<T> : IFoo
{
   public static explicit operator Foo<T>(T input) { whatever }
}
class Blah : Foo<IBar> {}
...
IBar bar = new Blah();  
Foo<IBar> foo = (Foo<IBar>)bar;

Now, does that call the user-defined explicit conversion or not? The object really is derived from Foo, so you would hope that it does not; this should be a simple type test and a reference assignment, not a call to a helper method. A cast on an interface value is always treated as a type test because it is almost always possible that the object really is of that type and really does implement that interface. We don't want to deny you the possibility of doing a cheap representation-preserving conversion.

Eric Lippert
Ok, the special rules about casting on interfaces, and the reason for these rules, helps me understand this. Thanks for clarifying, Eric.
Judah Himango
I should add, my code sample "feels" like it should work. Kind of like covariance, it's one of those things that your mind says ought to work, but doesn't. Heh.
Judah Himango
Does this mean that in the example above (Foo<IBar>)bar is treated as a "cast" instead of an explicit conversion?
Abhijeet Patel