Question regarding implicit conversions in the C# language specification
Asked Answered
A

4

11

Section 6.1 Implicit conversions defines an identity conversion thusly:

An identity conversion converts from any type to the same type. This conversion exists such that an entity that already has a required type can be said to be convertible to that type.

Now, what is the purpose of sentences such as these?

(In §6.1.6 Implicit reference conversions)

The implicit reference conversions are:

  • [...]
  • From any reference-type to a reference-type T if it has an implicit identity or reference conversion to a reference-type T0 and T0 has an identity conversion to T.

and:

(In §6.1.7 Boxing conversions)

  • A value type has a boxing conversion to an interface type I if it has a boxing conversion to an interface type I0 and I0 has an identity conversion to I.

Initially they seem redundant (tautologous). But they must be there for a purpose, so why are they there?

Can you give an example of two types T1, T2 such that T1 would not be implicitly convertible to T2 if it weren’t for the above-quoted paragraphs?

Ardehs answered 17/9, 2010 at 15:38 Comment(3)
Yes, it seems tautologous. According to the stated definition of "identity conversion", "T0 has an identity conversion to T" implies that T0 and T are the same type...Advise
Where's Eric Lippert when you need him? :)Ardehs
At the Western Washington fairgrounds, looking at fancy horses.Buckish
B
2

Section 4.7 of the specification notes that there is an identity conversion from Foo<dynamic> to Foo<object> and vice versa. The portion of the spec you quoted is written to ensure that this case is handled. That is, if there is an implicit reference conversion from T to C<object, object> then there is also an implicit reference conversion to C<object, dynamic>, C<dynamic, object> and C<dynamic, dynamic>.

One might reasonably point out that (1) the intention of these phrases is unobvious - hence your question - and confusing, and (2) that the section on identity conversions ought to cross-reference the section on dynamic conversions, and (3) phrases like this in the spec make it difficult for an implementor of the specification to clearly translate the spec language into an implementation. How is one to know if any such type exists? The spec need not specify exact algorithms, but it would be nice if it gave more guidance.

The spec is, sadly, not a perfect document.

Buckish answered 20/9, 2010 at 4:46 Comment(9)
@Eric — Would you happen to have an answer to my Expression Tree question too? I realise it’s more a framework question than a language design question though.Ardehs
@Timwi: I have done so. I note that the C# team designed and implemented this portion of the framework because it was driven directly by the language design. (Enhancements since that time were taken over by the DLR team.)Buckish
@Eric — I suspected as much, but from earlier experience I got the impression that you prefer if I not make assumptions or speculative guesses on this... :-)Ardehs
@Timwi: Make all the assumptions and speculations you like. To be clear: what I objected to was you telling my customers to not report my bugs to me.Buckish
@Eric — OK. Thanks for clearing that up. I hate to be intrusive, but I did report a small bug in the C# language specification (regarding the transitivity of the “dependence” definition in the section on type inference) to you via the web form on your blog — did you receive this? I was hoping to get a response, because then I would have reported a lot more. :)Ardehs
@Timwi: I did not. A nonzero fraction of mail sent through the blog doesn't make it through the spam filter for some reason. What's the bug?Buckish
@Eric: It’s just that in the second paragraph of §7.5.2.5 Dependence, there is an i and a j swapped. In general, are you interested in me sending such small bugreports to you are are they too minor? If you are interested, and the blog form is not reliable enough, where should I send them?Ardehs
@Timwi: Thanks for the report. I think we found that one already but it never hurts to double-check. And you do have my work email address, from when I replied to your question about closing over a loop variable.Buckish
@Eric: I don’t have your e-mail anymore due to a hard disk crash around that time. :( I’ll try to send another message through the blog form. I have a suggestion which I’m sure you’ll like :)Ardehs
G
6

Update on 22-Sep-2010:

I doubt anybody is going to read this besides Timwi. Even so, I wanted to make a few edits to this answer in light of the fact that a new answer has now been accepted and the debate still continues (at least in my perhaps imaginary world) on whether or not the quoted excerpts of the spec are technically redundant. I am not adding much, but it's too substantial to fit in a comment. The bulk of the update can be found under the heading "Conversion involving the dynamic type" below.


Update on 19-Sep-2010:

In your comment:

[T]his doesn’t make sense.

Damn, Timwi, you say that a lot. But all right, then; you've put me on the defensive, so here goes!

Disclaimer: I have definitely not examined the spec as closely as you have. Based on some of your recent questions it seems like you've been looking into it quite a bit lately. This is naturally going to make you more familiar with a lot of details than most users on SO; so this, like most answers you're likely to receive from anyone other than Eric Lippert, may not satisfy you.

Different premises

Firstly, the premise of your question is that if the statements highlighted are redundant, then they serve no purpose. My answer's premise is that redundant statements are not necessarily without purpose if they clarify something that isn't obvious to everyone. These are contradictory premises. And if we can't agree on premises, we can't have a straightforward logical argument. I was simply asking you to rethink your premise.

Your response, however, was to reiterate your premise: "If the sentences are truly redundant, then they only confuse the reader and don't clarify anything."

(I like how you set yourself up as the representative for all readers of the spec there, by the way.)

I can't blame you for holding this position, exactly. I mean, it does seem obvious. And I didn't give any concrete examples in my original answer. So below I will try to include some concrete examples. But first, let me take a step back and offer my take on why this weird identity conversion concept exists in the spec in the first place.

The purpose of the identity conversion definition

Upon first glance, this definition seems rather superfluous; isn't it just saying that an instance of any type T is convertible to ... well, to T? Yes, it is. But I hypothesize* that the purpose of this definition is to provide the spec with the proper vocabulary to utilize the concept of type identity in the context of discussing conversions.

This allows for statements about conversions which are essentially transitive in nature. The first point you quoted from the spec as an example of a tautological statement falls into this category. It says that if an implicit conversion is defined for some type (I'll call it K) to another type T0 and T0 has an identity conversion to T, then K is implicitly convertible to T. By the definition of identity conversion given above, "has an identity conversion to" really means "is the same type as." So the statement is redundant.

But again: the identity conversion definition exists in the first place to equip the spec with a formal language for describing conversions without having to say things like "if T0 and T are really the same type."

OK, time for concrete examples.

Where the existence of an implicit conversion might not be obvious to some developers

Note: A much better example has been provided by Eric Lippert in his answer to the question. I leave these first two examples as only minor reinforcements of my point. I have also added a third example that concretizes the identity conversion that exists between object and dynamic as pointed out in Eric's answer.

Transitive reference conversion

Let's say you have two types, M and N, and you've got an implicit conversion defined like this:

public static implicit operator M(N n);

Then you can write code like this:

N n = new N();
M m = n;

Now let's say you've got a file with this using statement up top:

using K = M;

And then you have, later in the file:

N n = new N();
K k = n;

OK, before I proceed, I realize that this is obvious to you and me. But my answer is, and has been from the beginning, that it might not be obvious to everyone, and therefore specifying it--while redundant--still has a purpose.

That purpose is: to make clear to anyone scratching his or her head, looking at that code, it is legal. An implicit conversion exists from N to M, and an identity conversion exists from M to K (i.e., M and K are the same type); so an implicit conversion exists from N to K. It isn't just logical (though it may be logical); it's right there in the spec. Otherwise one might mistakenly believe that something like the following would be necessary:

K k = (M)n;

Clearly, it isn't.

Transitive boxing conversion

Or take the type int. An int can be boxed as an IComparable<int>, right? So this is legal:

int i = 10;
IComparable<int> x = i;

Now consider this:

int i = 10;
IComparable<System.Int32> x = i;

Again, yes, it may be obvious to you, me, and 90% of all developers who might ever come across it. But for that slim minority who don't see it right away: a boxing conversion exists from int to IComparable<int>, and an identity conversion exists from IComparable<int> to IComparable<System.Int32> (i.e., IComparable<int> and IComparable<System.Int32> are the same type); so a boxing conversion exists from int to IComparable<System.Int32>.

Conversion involving the dynamic type

I'm going to borrow from my reference conversion example above and just tweak it slightly to illustrate the identity relation between object and dynamic in version 4.0 of the spec.

Let's say we have the types M<T> and N, and have defined somewhere the following implicit conversion:

public static implicit operator M<object>(N n);

Then the following is legal:

N n = new N();
M<dynamic> m = n;

Clearly, the above is far less obvious than the two previous examples. But here's the million-dollar question: would the above still be legal even if the excerpts from the spec quoted in the question did not exist? (I'm going to call these excerpts Q for brevity.) If the answer is yes, then Q is in fact redundant. If no, then it is not.

I believe the answer is yes.

Consider the definition of identity conversion, defined in section 6.1.1 (I am including the entire section here as it is quite short):

An identity conversion converts from any type to the same type. This conversion exists such that an entity that already has a required type can be said to be convertible to that type.

Because object and dynamic are considered equivalent there is an identity conversion between object and dynamic, and between constructed types that are the same when replacing all occurences of dynamic with object. [emphasis mine]

(This last part is also included in section 4.7, which defines the dynamic type.)

Now let's look at the code again. In particular I'm interested in this one line:

M<dynamic> m = n;

The legality of this statement (disregarding Q -- remember, the issue being discussed is the hypothetical legality of the above statement if Q did not exist), since M<T> and N are custom types, depends on the existence of a user-defined implicit conversion between N and M<dynamic>.

There exists an implicit conversion from N to M<object>. By the section of the spec quoted above, there is an identity conversion between M<object> and M<dynamic>. By the definition of identity conversion, M<object> and M<dynamic> are the same type.

So, just as in the first two (more obvious) examples, I believe it is true that an implicit conversion exists from N to M<dynamic> even without taking Q into account, just as it is true that an implicit conversion exists from N to K in the first example and that a boxing conversion exists from int to IComparable<System.Int32> in the second example.

Without Q, this is much less obvious (hence Q's existence); but that does not make it false (i.e., Q is not necessary for this behavior to be defined). It just makes it less obvious.

Conclusion

I said in my original answer that this is the "obvious" explanation, because it seemed to me you were barking up the wrong tree. You initially posed this challenge:

Can you give an example of two types T1, T2 such that T1 would not be implicitly convertible to T2 if it weren’t for the above-quoted paragraphs?

No one's going to meet this challenge, Timwi, because it's impossible. Take the first excerpt about reference conversions. It is saying that a type K is implicitly convertible to a type T if it is implicitly convertible to T0 and T0 is the same as T. Deconstruct this, put it back together, and you're left with an obvious tautology: K is implicitly convertible to T if it's implicitly convertible to T. Does this introduce any new implicit conversions? Of course not.

So maybe Ben Voigt's comment was correct; maybe these points that you're asking about would've been better placed in footnotes, rather than in the body of the text. In any case, it's clear to me that they are redundant, and so to start with the premise they cannot be redundant, or else they wouldn't be there is to embark on a fool's errand. Be willing to accept that a redundant statement may still shed some light on a concept that may not be obvious to everyone, and it will become easier to accept these statements for what they are.

Redundant? Yes. Tautologous? Yes. Pointless? In my opinion, no.

*Obviously, I did not have any part in writing the C# language specification. If I did, this answer would be a lot more authoritative. As it is, it simply represents one guy's feeble attempt to make sense of a rather complex document.


Original answer

I think you're (perhaps intentionally) overlooking the most obvious answer here.

Consider these two sentences in your question:

(1) Initially they seem redundant (tautologous). (2) But they must be there for a purpose, so why are they there?

To me, the implication of these two sentences together is that a tautologous statement serves no purpose. But just because a statement follows logically from established premises, that does not make it obvious to everyone. In other words even if (1) is true, the answer to (2) may simply be: to make the described behavior clear to anyone reading the spec.

Now you might argue that even if something is not obvious, it still does not belong in a specification if it is providing a redundant definition. To this potential objection, I can only say: be realistic. It's not really practical (in my opinion) to comb through a document stripping out all statements which are simply stating facts that could have been deduced from prior statements.

If this were a common practice, I think you'd find a lot of literature out there -- not just specs, but research papers, articles, textbooks, etc. -- would be a lot shorter, denser, and more difficult to understand.

So: yes, perhaps they are redundant. But that does not negate their purpose.

Griswold answered 17/9, 2010 at 18:7 Comment(15)
That's why standards have footnotes: to point out things that are implied by the normative specification but otherwise might not be obvious.Travail
@Ben: OK, but then it seems like the question is simply: "Why are these statements in the body of the spec instead of in a footnote?" -- which seems to be essentially a question of style. (But then, I've never written a specification, so maybe I just don't appreciate the importance of some existing standards in this realm?)Griswold
Sorry Dan, but this doesn’t make sense. If the sentences are truly redundant, then they only confuse the reader and don’t clarify anything; all the other bullet points (which I have not quoted) are much clearer, and cover all the obvious and several non-obvious cases. The ones I quoted wouldn’t be there if they didn’t cover a non-obvious case. I’m asking what this non-obvious case is.Ardehs
@Timwi: My point was that being redundant and being obvious are not the same thing. You are simply insisting that they are and concluding that my answer doesn't make sense. I've updated my response with some examples of cases where I think the behavior defined in the spec might not be obvious to everyone. You'll have to keep an open mind, though, because I think it would be a stretch for you (or me) to view either of my examples as anything less than obvious. But that only speaks to my (in-)ability to come up with good examples, not to my fundamental point: redundant != pointless.Griswold
I have to say, I am amazed and impressed at the amount of effort you put into updating that answer. I think you deserve some brownie points for that alone. Also, I didn’t realise that my statement that something “doesn’t make sense” was this contentious: I apologise if it hurt anyone’s feelings. — Despite all this, however, my intuition still told me that those statements have a real purpose and are not redundant, and as you can see from Eric Lippert’s answer that has appeared by now, I was right.Ardehs
+1 for putting up with him and the sensible philosopher's angle.Ryley
@Timwi: No worries about hurt feelings--I was simply defending my point. That said, I personally believe you are still wrong in rejecting my primary argument. You say your intuition told you "those statements have a real purpose and are not redundant "--see how you are almost imperceptibly combining these two concepts into one? Eric himself did not say the statements are non-redundant; he explained their purpose (which I never disputed). I honestly still think they are technically redundant; you could take them out and the behavior they describe would still be defined by the spec.Griswold
@Dan: They are not technically redundant. Read Eric’s answer again. To give just one example, without those statements, IEnumerable<string> would not implicitly convert to IEnumerable<dynamic>. Indeed these statements are new in version 4.0 of the spec: they are not in 3.0.Ardehs
@Timwi: Why would IEnumerable<string> not implicitly convert to IEnumerable<dynamic>? IEnumerable<string> is implicitly convertible to IEnumerable<object>. The spec indicates that there is an identity conversion between IEnumerable<object> and IEnumerable<dynamic>. This means IEnumerable<object> and IEnumerable<dynamic> are the same type. Therefore there is an implicit conversion from IEnumerable<string> to IEnumerable<dynamic>. I've read Eric's answer again and I still don't see how these are technically non-redundant.Griswold
@Timwi: I also don't see how the statements being new to version 4.0 proves they are not redundant. Of course the introduction of the dynamic type makes them appropriate because the identity conversion between object and dynamic is probably the least obvious example of an identity conversion there is. But it doesn't change the definition of identity conversion, which is enough to make an implicit conversion from IEnumerable<string> to IEnumerable<dynamic> legal, at least as far as I can tell. Am I just being dense?Griswold
@Dan: Different question: Why would these sentences be specifically added in 4.0, and why would Eric specifically mention they were added to allow conversion from IEnumerable<string> (the “T” in his answer) to IEnumerable<dynamic> (the “C<dynamic, dynamic>” etc. in his answer)? Is there something you know that Eric doesn’t? — To answer your question, your mistake is that “identity conversion” doesn’t mean that IEnumerable<object> and IEnumerable<dynamic> are “the same type” (I’m sure you know they are not — they have different compile-time behaviour).Ardehs
@Timwi: Do I know something that Eric doesn't? OK, let's not make ridiculous jokes. But to respond to your question: it makes sense to me that these sentences would specifically be added in 4.0 because that's when dynamic was added and the identity conversion between object and dynamic is not obvious (this is also why Eric would specifically mention Foo<dynamic>, etc.). I certainly don't want to put words in Eric's mouth, so I won't speculate whether he would say the statements are technically redundant or not. But all he did say was "to ensure that this case is handled."Griswold
@Timwi: About the identity conversion between object and dynamic: the spec says "dynamic is considered identical to object except in the following respects" and lists dynamic binding and the preference of dynamic over object for type inference as the only differences. It also says "the type dynamic is indistinguishable from object at run-time." It also says " Because object and dynamic are considered equivalent there is an identity conversion between object and dynamic." To me all the ingredients are there to deduce the behavior specified in the sentences we're debating.Griswold
@Dan: This thread is getting a bit long... do you use Skype? Shall we consider moving this conversation there? My Skype ID is timwiterby if you’re keen. In the meantime, I’ll read this interesting question you posted before and try to learn something from it :)Ardehs
@Timwi: Haha, are you insinuating something, Timwi? I am actually enjoying this conversation and would be happy to continue it on Skype; my ID is danielltao (note the extra L in the middle). Unfortunately to be honest I really shouldn't spend any more time on this today as I've got a load of schoolwork to do. But maybe tomorrow or sometime later on when we both have time we can pick up from where we've left off here.Griswold
B
2

Section 4.7 of the specification notes that there is an identity conversion from Foo<dynamic> to Foo<object> and vice versa. The portion of the spec you quoted is written to ensure that this case is handled. That is, if there is an implicit reference conversion from T to C<object, object> then there is also an implicit reference conversion to C<object, dynamic>, C<dynamic, object> and C<dynamic, dynamic>.

One might reasonably point out that (1) the intention of these phrases is unobvious - hence your question - and confusing, and (2) that the section on identity conversions ought to cross-reference the section on dynamic conversions, and (3) phrases like this in the spec make it difficult for an implementor of the specification to clearly translate the spec language into an implementation. How is one to know if any such type exists? The spec need not specify exact algorithms, but it would be nice if it gave more guidance.

The spec is, sadly, not a perfect document.

Buckish answered 20/9, 2010 at 4:46 Comment(9)
@Eric — Would you happen to have an answer to my Expression Tree question too? I realise it’s more a framework question than a language design question though.Ardehs
@Timwi: I have done so. I note that the C# team designed and implemented this portion of the framework because it was driven directly by the language design. (Enhancements since that time were taken over by the DLR team.)Buckish
@Eric — I suspected as much, but from earlier experience I got the impression that you prefer if I not make assumptions or speculative guesses on this... :-)Ardehs
@Timwi: Make all the assumptions and speculations you like. To be clear: what I objected to was you telling my customers to not report my bugs to me.Buckish
@Eric — OK. Thanks for clearing that up. I hate to be intrusive, but I did report a small bug in the C# language specification (regarding the transitivity of the “dependence” definition in the section on type inference) to you via the web form on your blog — did you receive this? I was hoping to get a response, because then I would have reported a lot more. :)Ardehs
@Timwi: I did not. A nonzero fraction of mail sent through the blog doesn't make it through the spam filter for some reason. What's the bug?Buckish
@Eric: It’s just that in the second paragraph of §7.5.2.5 Dependence, there is an i and a j swapped. In general, are you interested in me sending such small bugreports to you are are they too minor? If you are interested, and the blog form is not reliable enough, where should I send them?Ardehs
@Timwi: Thanks for the report. I think we found that one already but it never hurts to double-check. And you do have my work email address, from when I replied to your question about closing over a loop variable.Buckish
@Eric: I don’t have your e-mail anymore due to a hard disk crash around that time. :( I’ll try to send another message through the blog form. I have a suggestion which I’m sure you’ll like :)Ardehs
B
1

An identity conversion converts from any type to the same type. This conversion exists such that an entity that already has a required type can be said to be convertible to that type.

This says that in C#-land, 1==1; a Spade is a Spade. This is the basis of assigning an object reference to a variable of the same type; if a variable typed T1 and one typed T2 are in reality both Spades, it is possible to assign one to the other without having to explicitly cast one as a Spade. Imagine a C# variant where assignments had to look like this:

Spade mySpade = new Spade();
Spade mySpade2;

mySpade2 = (Spade)mySpade; //explicit cast required

Also, an "identity" in mathematics states that an expression that evaluates to a result given a set of inputs is equivalent to another expression that produces the same result given the same inputs. In programming, this means that an expression or function that evaluates to an instance of a type is equivalent to that type, without explicit conversion. If that didn't hold, the following code would be required:

public int myMethod() { /*Do something*/ }
...
int myInt = (int)myMethod(); //required even though myMethod() evals to an int.
...
int myInt = (int)(1 + 2); //required even though 1, 2, and 1+2 eval to an int.

The second rule basically says that a value type can be assigned to a member variable on a class if, in part, the member variable (a boxed type by definition, as its container is a reference type) is declared to be the same type. If this rule were not specified, theoretically, a version of C# could exist in which pure value types would have to be explicitly converted to their reference analog in order to be stored as a member variable on a class. Imagine, for example, a version of C# in which the blue keyword types (int, float, decimal) and the light blue class names (Int32, Float, Decimal) referred to two very different, only-explicitly-convertible types, and int, float, decimal etc. were not legal as member variable types because they were not reference types:

public class MyClass
{
  Int32 MyBoxedValueType; //using "int" not legal
}

...

MyClass myClass = new MyClass();
int theInt = 2;

myClass.MyBoxedValueType = (Int32)theInt; //explicit cast required

I know it sounds silly, but at some level, these things must be known, and in computers, you have to specify EVERYTHING. Read the USA Hockey rulebook for ice hockey sometime; the very first rule in the book is that the game shall be played on an ice surface. It's one of the ultimate "well duhs", but also a fundamental truth of the game that must be understood in order for any other rule to make sense.

Benediction answered 17/9, 2010 at 16:44 Comment(5)
KeithS, many thanks for your answer, I really appreciate the effort you put into this. But unfortunately you have not answered my question. All of the conversions you have given are covered by other points in the spec. For instance, the implicit conversion to object and ValueType is already covered by the first paragraph of §6.1.7: “A boxing conversion exists from any non-nullable-value-type to object and dynamic, to System.ValueType and to any interface-type implemented by the non-nullable-value-type. Furthermore an enum-type can be converted to the type System.Enum.”Ardehs
The conversions from a class to a base class and from a class to an interface it implements, are covered by the second and third bullet point in §6.1.6: “• From any class-type S to any class-type T, provided S is derived from T. • From any class-type S to any interface-type T, provided S implements T.”Ardehs
@Timwi: OK, but the rule still says that 1==1; an implicit conversion between one type and the same type exists, meaning that Spade newSpade = theSpade; is legal without needing to cast theSpade to a Spade if it already is one. And the boxing rule states that a value type can be boxed as a reference analog of the same defined type; an int on the stack can be assigned as an int member variable of a reference type. These two rules define the basis by which inheritance-based implicit conversions are allowed; if you have to cast a Spade to itself, you can't implicitly cast a CardSuit.Benediction
@KeithS: Sorry, but you don’t know what you are talking about. Member variables of type int in a reference type do not undergo boxing. There is such thing in C# as “a reference analog of the same defined type” — if it’s the same type as a value type, then it’s obviously a value type and it is not boxed. As I already mentioned, inheritance-based conversions are already defined elsewhere. You have not answered my question at all.Ardehs
YOU are the one who doesn't know what you're talking about. A value type that is a member of a reference type is boxed by definition; it is stored on the heap with the rest of the instance's state, not the stack like a local value type. What I was trying to say is that without this rule stating explicitly that a type can be implicitly boxed as itself, there would have to be an explicit conversion in order to assign a boxed variable to an unboxed one, or vice-versa. The fact that there is such an implicit conversion is the reason there ISN'T a seperate "reference analog" type.Benediction
H
-1

May it is such that the code guarantees pass-through when called like Convert.ChangeType(client, typeof(Client)) regardless if IConvertible is implemented.

Look into the source of ChangeType from mscorlib with Reflector and notice the conditions at which value is returned as-is.

Remember a = operator is not a conversion, just a reference set. So code like Client client_2 = client_1 does not perform any implicit conversions. If an identity implicit conversion is declared then error CS0555 is issued.

I guess the spec says let the C# compiler handle such cases, and thus dot not manually try to define identity conversions.

Hyperkinesia answered 19/9, 2010 at 5:24 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.