25

Given this highly simplified example:

abstract class Animal { }

class Dog : Animal
{
  public void Bark() { }
}
class Cat : Animal
{
  public void Mew() { }
}

class SoundRecorder<T> where T : Animal
{
  private readonly T _animal;

  public SoundRecorder(T animal) { _animal = animal; }

  public void RecordSound(string fact)
  {
    if (this._animal is Dog)
    {
      ((Dog)this._animal).Bark(); // Compiler: Cannot convert type 'T' to 'Dog'.
      ((Dog)(Animal)this._animal).Bark(); // Compiles OK
    }
  }
}

Why does the compiler complain about the single type cast (Dog)this._animal? I just can't get why compiler seems to need help by doing two casts. _animal cannot be anything else than an Animal, can it?

Of course this question is motivated by a real life example where I had to modify existing code in a way that a similar cast was the most convenient way to do it, without refactoring the whole lot. (Yes, using composition rather than inheritance ;) ).

Gert Arnold
  • 105,341
  • 31
  • 202
  • 291
  • 1
    Why don't you move the `SoundRecorder` method to the interface and both classes implements it!? so u can call it easily! – Ahmed Magdy Nov 02 '11 at 10:03
  • 10
    @AhmedMagdy: I could, I could do many more things, but that's not the question. – Gert Arnold Nov 02 '11 at 10:43
  • 1
    Note that even though you have two casts, the compiler only emits a single "castclass Dog" IL instruction. – Random832 Nov 02 '11 at 13:20
  • @Random832: You're actually right! I must admit, I hardly ever inspect IL code, but this observation is kind of funny. I think that writing IL code can be optimized after the compilation process has passed all validations. – Gert Arnold Nov 02 '11 at 13:39
  • @GertArnold: I've now edited my answer - could you see whether it makes any sense to you? – Jon Skeet Nov 03 '11 at 07:35

5 Answers5

34

EDIT: This is an attempted restatement of Polity's answer - I think I know what he's trying to say, but I could be wrong.

My original answer (below the line) is still in some ways the canonical one: the compiler rejects it because the language specification says it has to :) However, in an attempt to guess the view of the language designers (I've never been part of the C# design committee, and I don't think I've asked them about this, so it really is guesswork...) here goes...

We're used to thinking about the validity of conversions "at compile time" or "at execution time". Usually implicit conversions are ones which are compile-time-guaranteed to be valid:

string x = "foo";
object y = x;

That can't go wrong, so it's implicit. If something can go wrong, the language is designed so that you have to tell the compiler, "Trust me, I believe it'll work at execution time even though you can't guarantee it now." Obviously there's a check at execution time anyway, but you're basically telling the compiler you know what you're doing:

object x = "foo";
string y = (string) x;

Now the compiler already prevents you from attempting conversions which it believes can never work1 in a useful way:

string x = "foo";
Guid y = (Guid) x;

The compiler knows there's no conversion from string to Guid, so the compiler doesn't believe your protestations that you know what you're doing: you clearly don't2.

So those are the simple cases of "compile time" vs "execution time" checking. But what about generics? Consider this method:

public Stream ConvertToStream<T>(T value)
{
    return (Stream) value;
}

What does the compiler know? Here we have two things which can vary: the value (which varies at execution time, of course) and the type parameter T, which is specified at a potentially different compile time. (I'm ignoring reflection here, where even T is only known at execution time.) We may compile the calling code later, like this:

ConvertToStream<string>(value);

At that point, the method doesn't make sense if you replace the type parameter T with string, you end up with code which wouldn't have compiled:

// After type substitution
public Stream ConvertToStream(string value)
{
    // Invalid
    return (Stream) value;
}

(Generics don't really work by doing this sort of type substitution and recompiling, which would affect overloading etc - but it can sometimes be a helpful way of thinking about it.)

The compiler can't report that at the time when the call is compiled - the call doesn't violate any constraints on T, and the body of the method should be viewed as an implementation detail. So if the compiler wants to prevent the method from ever being called in a way which introduces a non-sensical conversion, it has to do so when the method itself is compiled.

Now the compiler/language isn't always consistent in this approach. For example, consider this change to the generic method, and the "following type substitution when called with T=string" version:

// Valid
public Stream ConvertToStream<T>(T value)
{
    return value as Stream;
}

// Invalid
public Stream ConvertToStream(string value)
{
    return value as Stream;
}

This code does compile in the generic form, even though the version after type substitution doesn't. So maybe there's a deeper reason. Maybe in some cases there simply wouldn't be suitable IL to represent the conversion - and the easier cases aren't worth making the language more complicated for...

1 It sometimes gets this "wrong", in that there are times when a conversion is valid in the CLR but not in C#, such as int[] to uint[]. I'll ignore these edge cases for the moment.

2 Apologies to those who dislike the anthropomorphisation of the compiler in this answer. Obviously the compiler doesn't really have any emotional view of the developer, but I believe it helps get the point across.


The simple answer is that the compiler complains because the language specification says it has to. The rules are given in section 6.2.7 of the C# 4 spec.

The following explicit conversions exist for a given type parameter T:

...

  • From a type parameter U to T, provided T depends on U. (See section 10.1.5.)

Here Dog doesn't depend on T, so there's no conversion allowed.

I suspect this rule is in place to avoid some obscure corner cases - in this case it's a bit of a pain when you can logically see that it should be a valid attempted conversion, but I suspect that codifying that logic would make the language more complicated.

Note that an alternative might be to use as instead of is-then-cast:

Dog dog = this._animal as Dog;
if (dog != null)
{
    dog.Bark();
}

I'd argue that's cleaner anyway, in terms of only performing the conversion once.

Community
  • 1
  • 1
Jon Skeet
  • 1,421,763
  • 867
  • 9,128
  • 9,194
  • Probably something to live with then, there are worse things... You're right about the `as` keyword, but I try to use explicit casts wherever I can. – Gert Arnold Nov 02 '11 at 10:06
  • @GertArnold: That's fine when it's an **unconditional** cast - but I prefer `as` to `is`-followed-by-cast. – Jon Skeet Nov 02 '11 at 10:08
  • 1
    Jon, super! Now you've really explored the *obscure corner cases* you already mentioned. I feel honoured to get such an elaborate answer to my question. Thanks! – Gert Arnold Nov 03 '11 at 08:16
14

The problem is that the compiler can't guarantee that _animal can be casted to Dog since the only restriction you give the type parameter of SoundRecorded is that the type should be Animal OR inherit from Animal. So the compiler is practically thinking: what if you construct a SoundRecorder<Cat>, the cast operation then is invalid.

Unfortunatly (or not), the compiler isn't smart enough to see that you safely protected your code from ever reaching there by doing the 'is' check in advance.

If you were to store the given animal as an actual animal, this wouldn't be a problem since the compiler always allows any cast from a base type to a derived type. The compiler doesnt allow a cast from Dog to Cat though

EDIT See Jon Skeets answer for a more concrete explanation.

Polity
  • 14,734
  • 2
  • 40
  • 40
  • 2
    You could argue that `(Animal)this._animal` could still be a Cat and that the cast to dog is invalid. Sadly I suspect the answer is more along the lines of "because the spec says so" with the actual underlying reasons being a bit more complicated. :) – Chris Nov 02 '11 at 10:00
  • The bit *OR inherit from Animal* is something I overlooked, but indeed, this should not be a problem. – Gert Arnold Nov 02 '11 at 10:01
  • Jon has the most complete answer (who else), but your answer was first and it pointed out something that makes me understand this. So: accepted. Thanks. – Gert Arnold Nov 02 '11 at 10:24
  • 1
    I disagree with this answer, as it happens. The compiler not being able to guarantee that the conversion will work at execution time is a good reason for it not to be an *implicit* conversion - but *explicit* conversions have that risk all over the place. I can write: `object x = "hello"; string y = (string) x;` and again, the compiler can't guarantee that `x` can be cast to `string`. How is *that* part different here? I suspect there's an element of truth in what you've written in that a particular type argument may make the conversion *always fail*, but I it needs more careful expression. – Jon Skeet Nov 02 '11 at 11:07
  • @JonSkeet: I think the difference is that `int x = 1; string y = (string)x;` does not compile, while `int x = 1; string y = (string)(object)x;` does. That was my epiphany here. – Gert Arnold Nov 02 '11 at 11:39
  • @GertArnold: Yes, I think there's something of value in this answer, but it's not being expressed as clearly as it might be IMO. – Jon Skeet Nov 02 '11 at 11:41
  • @JonSkeet: That's probably why you write best sellers :). – Gert Arnold Nov 02 '11 at 11:43
  • @JonSkeet - You are right, i will edit my answer to forward readers to yours for more information – Polity Nov 02 '11 at 15:03
  • @Polity: My answer doesn't capture what I think you're trying to get at... but would you mind if I edited my answer to add what I *believe* you're trying to convey? – Jon Skeet Nov 02 '11 at 15:08
  • @JonSkeet - Please jon. i know what your saying but i'm not a good teacher. If your edits conflict with my thinking i'll let you know – Polity Nov 02 '11 at 15:57
  • @Polity: Righto. I'll be editing *my* answer rather than yours, btw - I don't want to put words into your mouth. Oh, and it won't be for a few hours, by which time Eric Lippert may have come along and given a more definitive answer anyway :) – Jon Skeet Nov 02 '11 at 15:59
  • @Polity: Later than expected, but I'm done. Could you have a look and see whether that makes sense? – Jon Skeet Nov 03 '11 at 07:35
2

This is probably because you are specifying that the generic type extends Animal, so SoundRecorder could be instanciated with Cat as the generic type. Therefore, the compiler cannot permit you to cast an arbitrary subclass of Animal to some other subclass of Animal. If you want to avoid the double cast, try doing the following:

var dog = _animal as Dog;

if(dog != null)
{
    dog.Bark();
}

This article touches on the topic of casting generic parameters

flipchart
  • 6,548
  • 4
  • 28
  • 53
1

There is no explicit type conversion exist between Animal to Dog since your constrains says T must be of type Animal. Though Dog ‘Is a’ an Animal , the compiler doesn't know that T is Dog. Therefore, it doesn't let you cast.

You can either approach this through implicit conversion

implicit operator Animal(Dog myClass) 

or can use something like below

Dog d = _animal as Dog;
s_nair
  • 812
  • 4
  • 12
0

As of C# 7.0 you can now use declaration pattern to check the run-time type of an expression and, if a match succeeds, assign an expression result to a declared variable.

public void RecordSound()
{
    if (_animal is Dog dog)
    {
        dog.Bark();
    }
}

Just wanted to mention this, even though it does not answer the 'why' part of your question. There are already some great answers here for this part.

Jogge
  • 1,654
  • 12
  • 36