Okay. I've read this post, and I'm confused on how it applies to my example (below).
class Foo { public static implicit operator Foo(IFooCompatible fooLike) { return fooLike.ToFoo(); } } interface IFooCompatible { Foo ToFoo(); void FromFoo(Foo foo); } class Bar : IFooCompatible { public Foo ToFoo() { return new Foo(); } public void FromFoo(Foo foo) { } } class Program { static void Main(string[] args) { Foo foo = new Bar(); // should be the same as: // var foo = (new Bar()).ToFoo(); } }
I have thoroughly read the post I linked to. I have read section 10.10.3 of the C# 4 specification. All of the examples given relate to generics and inheritance, where the above does not.
Can anyone explain why this is not allowed in the context of this example?
Please no posts in the form of "because the specification says so" or that simply quote the specification. Obviously, the specification is insufficient for my understanding, or else I would not have posted this question.
Edit 1:
I understand that it's not allowed because there are rules against it. I am confused as to why it's not allowed.
An implicit conversion sequence is the sequence of conversions required to convert an argument in a function call to the type of the corresponding parameter in a function declaration. The compiler tries to determine an implicit conversion sequence for each argument.
Use the operator and implicit or explicit keywords to define an implicit or explicit conversion, respectively. The type that defines a conversion must be either a source type or a target type of that conversion. A conversion between two user-defined types can be defined in either of the two types.
The Implicit Operator According to MSDN, an implicit keyword is used to declare an implicit user-defined type conversion operator. In other words, this gives the power to your C# class, which can accepts any reasonably convertible data type without type casting.
An implicit conversion from type S to type T is defined by an implicit value which has function type S => T , or by an implicit method convertible to a value of that type. Implicit conversions are applied in two situations: If an expression e is of type S , and S does not conform to the expression's expected type T .
I understand that it's not allowed because there are rules against it. I am confused as to why it's not allowed.
The general rule is: a user defined conversion must not in any way replace a built-in conversion. There are subtle ways that this rule can be violated involving generic types, but you specifically say that you are not interested in generic type scenarios.
You cannot, for example, make a user-defined conversion from MyClass
to Object
, because there already is an implicit conversion from MyClass
to Object
. The "built in" conversion will always win, so allowing you to declare a user-defined conversion would be pointless.
Moreover, you cannot even make a user-defined implicit conversion that replaces a built-in explicit conversion. You cannot, for example, make a user-defined implicit conversion from Object
to MyClass
because there already is a built-in explicit conversion from Object
to MyClass
. It is simply too confusing to the reader of the code to allow you to arbitrarily reclassify existing explicit conversions as implicit conversions.
This is particularly the case where identity is involved. If I say:
object someObject = new MyClass(); MyClass myclass = (MyClass) someObject;
then I expect that this means "someObject
actually is of type MyClass
, this is an explicit reference conversion, and now myclass
and someObject
are reference equal". If you were allowed to say
public static implicit operator MyClass(object o) { return new MyClass(); }
then
object someObject = new MyClass(); MyClass myclass = someObject;
would be legal, and the two objects would not have reference equality, which is bizarre.
Already we have enough rules to disqualify your code, which converts from an interface to an unsealed class type. Consider the following:
class Foo { } class Foo2 : Foo, IBlah { } ... IBlah blah = new Foo2(); Foo foo = (Foo) blah;
This works, and one reasonably expects that blah
and foo
are reference equals because casting a Foo2 to its base type Foo does not change the reference. Now suppose this is legal:
class Foo { public static implicit operator Foo(IBlah blah) { return new Foo(); } }
If that is legal then this code is legal:
IBlah blah = new Foo2(); Foo foo = blah;
we have just converted an instance of a derived class to its base class but they are not reference equal. This is bizarre and confusing, and therefore we make it illegal. You simply may not declare such an implicit conversion because it replaces an existing built-in explicit conversion.
So alone, the rule that you must not replace any built-in conversion by any user-defined conversion is sufficient to deny you the ability to create a conversion that takes an interface.
But wait! Suppose Foo
is sealed. Then there is no conversion between IBlah
and Foo
, explicit or implicit, because there cannot possibly by a derived Foo2
that implements IBlah
. In this scenario, should we allow a user-defined conversion between Foo
and IBlah
? Such a user-defined conversion cannot possibly replace any built-in conversion, explicit or implicit.
No. We add an additional rule in section 10.10.3 of the spec that explicitly disallows any user-defined conversion to or from an interface, regardless of whether this replaces or does not replace a built-in conversion.
Why? Because one has the reasonable expectation that when one converts a value to an interface, that you are testing whether the object in question implements the interface, not asking for an entirely different object that implements the interface. In COM terms, converting to an interface is QueryInterface
-- "do you implement this interface?" -- and not QueryService
-- "can you find me someone who implements this interface?"
Similarly, one has a reasonable expectation that when one converts from an interface, one is asking whether the interface is actually implemented by an object of the given target type, and not asking for an object of the target type that is entirely different from the object that implements the interface.
Thus, it is always illegal to make a user-defined conversion that converts to or from an interface.
However, generics muddy the waters considerably, the spec wording is not very clear, and the C# compiler contains a number of bugs in its implementation. Neither the spec nor the implementation are correct given certain edge cases involving generics, and that presents a difficult problem for me, the implementer. I am actually working with Mads today on clarifying this section of the spec, as I am implementing it in Roslyn next week. I will attempt to do so with as few breaking changes as possible, but a small number may be necessary in order to bring the compiler behaviour and the specification language in line with each other.
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.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With