C# Compiler Bug? Why Doesn't This Implicit User-Defined Conversion Compile

C# compiler bug? Why doesn't this implicit user-defined conversion compile?

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.

Why C# compiler doesn't call implicit operator. Compiler bug?

This is explained in the Inferred Return Type section of the language spec.

During type inference, the compiler has to figure out what the return type for the lambdas you pass to Task.Run are, in order to infer the generic parameter for Task.Run. The rules are (for a lambda F):

  • If the body of F is an expression that has a type, then the inferred result type of F is the type of that expression.
  • If the body of F is a block and the set of expressions in the block's return statements has a best common type T, then the inferred result type
    of F is T.
  • Otherwise, a result type cannot be inferred for F.

For PleaseDoNotCompile, point 1 applies, and the return type is inferred to be Guid, so a Task<Guid> is returned by Task.Run. Note that the fact that you are assigning to a Task<SomeWrapper> is not taken into account, as usual in type inference. For example:

static void Main(string[] args)
{
string t = F(); // cannot infer type!
}

public static T F<T>()
{
return default(T);
}

In WhyDoYouCompile, the second point applies, and the compiler finds a "best common type" between the type of Guid.NewGuid() and new SomeWrapper(). Even though the second return is not reachable, the compiler still considers it in this process. It sounds silly I know, but that's the spec!

The rules for finding a best common type are specified here. It involves quite a bit of the type inference algorithm, so I won't explain it in detail here. I hope that you'll agree that intuitively, the best common type between Guid and SomeWrapper is SomeWrapper, since Guid can be converted to SomeWrapper.

Therefore, the generic parameter for Task.Run gets inferred as SomeWrapper, and you get a Task<SomeWrapper> as expected.

To get the expression-bodied lambda to work, you can simply specify the type parameters for Task.Run:

Task.Run<SomeWrapper>(() => Guid.NewGuid())

C# Compiler bug: allows for conversion from Nullabledecimal to decimal

According to specification lifted conversion operators are allowed only for value type to value type conversions (and their nullable counterparts) but in the Roslyn compiler source code you can find next comment:

DELIBERATE SPEC VIOLATION:

The native compiler allows for a "lifted" conversion even when the return type of the conversion not a non-nullable value type. For example, if we have a conversion from struct S to string, then a "lifted" conversion from S? to string is considered by the native compiler to exist, with the semantics of "s.HasValue ? (string)s.Value : (string)null". The Roslyn compiler perpetuates this error for the sake of backwards compatibility.

So it seems to be an actuall bug which was introduced for backwards compatibility. And the decompilation shows exactly that behaviour.

More on implicit conversion operators and interfaces in C# (again)

The context of your example, it won't work again because the implicit operator has been placed against an interface... I'm not sure how you think your sample is different to the one you linked other than you try to get one concrete type across to another via an interface.

There is a discussion on the topic here on connect:

http://connect.microsoft.com/VisualStudio/feedback/details/318122/allow-user-defined-implicit-type-conversion-to-interface-in-c

And Eric Lippert might have explained the reason when he said in your linked question:

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.

It seems to be to do with type identity. Concrete types relate to each other via their hierarchy so type identity can be enforced across it. With interfaces (and other blocked things such as dynamic and object) type identity becomes moot because anyone/everyone can be housed under such types.

Why this is important, I have no idea.

I prefer explicit code that shows me I am trying to get a Foo from another that is IFooCompatible, so a conversion routine that takes a T where T : IFooCompatible returning Foo.

For your question I understand the point of discussion, however my facetious response is if I see code like Foo f = new Bar() in the wild I would very likely refactor it.


An alternative solution:

Don't over egg the pudding here:

Foo f = new Bar().ToFoo();

You have already exposed the idea that Foo compatible types implement an interface to achieve compatibility, use this in your code.


Casting versus converting:

It is also easy to get wires crossed about casting versus converting. Casting implies that type information is integral between the types you are casting around, hence casting doesn't work in this situation:

interface IFoo {}
class Foo : IFoo {}
class Bar : IFoo {}

Foo f = new Foo();
IFoo fInt = f;
Bar b = (Bar)fInt; // Fails.

Casting understands the type hierarchy and the reference of fInt cannot be cast to Bar as it is really Foo. You could provide a user-defined operator to possibly provide this:

public static implicit operator Foo(Bar b) { };

And doing this in your sample code works, but this starts to get silly.

Converting, on the other hand, is completely independent of the type hierarchy. Its behaviour is entirely arbitrary - you code what you want. This is the case you are actually in, converting a Bar to a Foo, you just happen to flag convertible items with IFooCompatible. That interface doesn't make casting legal across disparate implementing classes.


As for why interfaces are not allowed in user-defined conversion operators:

Why can't I use interface with explicit operator?

The short version is that it's disallowed so that the user can be
certain that conversions between reference types and interfaces
succeed if and only if the reference type actually implements that
interface, and that when that conversion takes place that the same
object is actually being referenced.

C# generic implicit cast on Interface failed

User defined conversions aren't allowed on interfaces at all. It would potentially be ambiguous, because the type you're trying to convert from could implement the interface itself - at which point what would the cast mean? A reference conversion like a normal cast, or an invocation of the user-defined conversion?

From section 10.3.3 of the C# 4 spec:

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.

and then later:

However, it is possible to declare operators on generic types that, for particular type arguments, specify conversions that already exist as pre-defined conversions

...

In cases where a pre-defined conversion exists between two types, any user-defined conversions between those types are ignored. Specifically:

  • If a pre-defined implicit conversion (§6.1) exists from type S to type T, all user-defined conversions (implicit or explicit) from S to T are ignored.
  • If a pre-defined explicit conversion (§6.2) exists from type S to type T, any user-defined explicit conversions from S to T are ignored. Furthermore:

    • If T is an interface type, user-defined implicit conversions from S to T are ignored.
    • Otherwise, user-defined implicit conversions from S to T are still considered.

Note the first nested bullet here.

(I can thoroughly recommend getting hold of the spec by the way. It's available online in various versions and formats, but the hardcopy annotated edition is also a goldmine of little nuggets from the team and others. I should confess a certain bias here, as I'm one of the annotators - but ignoring my stuff, all the other annotations are well worth reading!)

Why does this implicit type conversion in C# fail?

I believe it's because IX is an interface. The compiler thinks that maybe a value of type IX could already be derived from Wrapped<IX> (even if Wrapped<T> is sealed) so it doesn't use the conversion.

The details are quite complicated, in sections 6.4.3 and 6.4.4 of the C# 3.0 spec. Basically because IX is an interface, it's not "encompassed by" any types, which means a later step in 6.4.4 fails.

I suggest you create a non-generic type Wrapped with this method:

public static Wrapped<T> Of<T>(T item)
{
return new Wrapped<T>(item);
}

Then you can just write:

using (Wrapped<IX> wrappedIX = Wrapped.Of(plainIX))

Basically conversions can be a bit tricky for various reasons - simple methods are generally easier to understand, IMO.

Why does an implicit conversion operator from T to U accept T??

You can take a look at how compiler lowers this code:

int? a = 3;
Sample<int> sampleA = a;

into this:

int? nullable = 3;
int? nullable2 = nullable;
Sample<int> sample = nullable2.HasValue ? ((Sample<int>)nullable2.GetValueOrDefault()) : null;

Because Sample<int> is a class its instance can be assigned a null value and with such an implicit operator the underlying type of a nullable object can also be assigned. So assignments like these are valid:

int? a = 3;
int? b = null;
Sample<int> sampleA = a;
Sample<int> sampleB = b;

If Sample<int> would be a struct, that of course would give an error.

EDIT:
So why is this possible? I couldn't find it in spec because it's a deliberate spec violation and this is only kept for backwards compatibility. You can read about it in code:

DELIBERATE SPEC VIOLATION:

The native compiler allows for a "lifted" conversion even when the return type of the conversion not a non-nullable value type. For example, if we have a conversion from struct S to string, then a "lifted" conversion from S? to string is considered by the native compiler to exist, with the semantics of "s.HasValue ? (string)s.Value : (string)null". The Roslyn compiler perpetuates this error for the sake of backwards compatibility.

That's how this "error" is implemented in Roslyn:

Otherwise, if the return type of the conversion is a nullable value type, reference type or pointer type P, then we lower this as:

temp = operand
temp.HasValue ? op_Whatever(temp.GetValueOrDefault()) : default(P)

So according to spec for a given user-defined conversion operator T -> U there exists a lifted operator T? -> U? where T and U are non-nullable value types. However such logic is also implemented for a conversion operator where U is a reference type because of the above reason.

PART 2 How to prevent the code from compiling in this scenario? Well there is a way. You can define an additional implicit operator specifically for a nullable type and decorate it with an attribute Obsolete. That would require the type parameter T to be restricted to struct:

public class Sample<T> where T : struct
{
...

[Obsolete("Some error message", error: true)]
public static implicit operator Sample<T>(T? value) => throw new NotImplementedException();
}

This operator will be chosen as a first conversion operator for nullable type because it's more specific.

If you can't make such a restriction you must define each operator for each value type separately (if you are really determined you can take advantage of reflection and generating code using templates):

[Obsolete("Some error message", error: true)]
public static implicit operator Sample<T>(int? value) => throw new NotImplementedException();

That would give an error if referenced in any place in code:

Error CS0619 'Sample.implicit operator Sample(int?)' is obsolete: 'Some error message'

Serious bugs with lifted/nullable conversions from int, allowing conversion from decimal

Your second portion (using nullable types) appears to be very similar to this known bug in the current compiler. From the response on the Connect issue:

While we do not currently have plans to address this issue in the next release of Visual Studio, we do plan to investigate a fix in Roslyn

As such, this bug will hopefully get corrected in a future release of Visual Studio and the compilers.

Implicit operator has error when converting to custom defined type

You cannot declare an implicit conversion to one of your own base classes. System.Object is always your (direct or indirect) base class. Through inheritance there's already an implicit conversion to the base class. So how could there be two implicit conversions? The C# spec has a good reson to disallow this!



Related Topics



Leave a reply



Submit