Question regarding implicit conversions in the C#

2019-03-18 16:10发布

问题:

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?

回答1:

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.



回答2:

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.



回答3:

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.



回答4:

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.