简体   繁体   中英

Why does the C# compiler allow a cast to be performed with Linq but not with parentheses?

I have a generic class NamedValue<TValue> :

public class NamedValue<TValue>
{
    public string Name { get; set; }
    public TValue Value { get; set; }
}

I have a second generic class, NamedValueSource<TValue> that contains a List<NamedValue<TValue>> :

public class NamedValueSource<TValue>
{
    public List<NamedValue<TValue>> NamedValues { get; set; }

    public NamedValueSource()
    {
        NamedValues = GetNamedValues().Cast<NamedValue<TValue>>().ToList();
    }

    private IEnumerable<NamedValue<bool>> GetNamedValues()
    {
        var yesNamedValue = new NamedValue<bool> { Name = "Yes", Value = true };
        var noNamedValue = new NamedValue<bool> { Name = "Yes", Value = false };
        yield return yesNamedValue;
        yield return noNamedValue;
    }
}

The following test code works perfectly (the assertion passes):

public class Tester
{
    public Tester()
    {
        var source = new NamedValueSource<bool>();
        Debug.Assert(source.NamedValues[0].Name == "Yes");
    }
}

Now, here's the interesting part. If I attempt to perform the cast within GetNamedValues() , the code won't compile:

public class NamedValueSourceFail<TValue>
{
    public List<NamedValue<TValue>> NamedValues { get; set; }

    public NamedValueSourceFail()
    {
        NamedValues = GetNamedValues().ToList();
    }

    private IEnumerable<NamedValue<TValue>> GetNamedValues()
    {
        var yesNamedValue = new NamedValue<bool> { Name = "Yes", Value = true };
        var noNamedValue = new NamedValue<bool> { Name = "Yes", Value = false };
        yield return (NamedValue<TValue>)yesNamedValue; // ERROR: cannot convert type
        yield return (NamedValue<TValue>)noNamedValue; // ERROR: cannot convert type
    }
}

Why does NamedValueSource<TValue> compile while NamedValueSourceFail<TValue> errors out? Specifically, why am I able to perform the cast using Linq but not with good ol' parantheses?

Edit

In case it's not entirely clear from the comment thread of the accepted answer, I simply needed to convert to object first, then I would be allowed to cast to NamedValue<TValue> . This is probably how the Linq Cast method works behind the scenes.

UPDATE : This question was the subject of my blog on July 10th 2012 ; thanks for the great question!


Let's greatly simplify your complicated program.

public static class X
{
    public static V Cast<V>(object o) { return (V)o; }
}

class C<T> {}
class D<U>
{
    public C<U> value;
    public D()
    {
        this.value = X.Cast<C<U>>(new C<bool>());
    }
}

Now your second version, simplified:

class C<T> {}
class D<U>
{
    public C<U> value;
    public D()
    {
        this.value = (C<U>)(new C<bool>());
    }
}

OK, so now let's ask some questions.

Why does the second program fail at compile time?

Because there is no conversion from C<bool> to C<U> for arbitrary U . The compiler knows that the only way this could possibly succeed is is U is always bool, and therefore this program is almost certainly wrong! The compiler assumes that U is going to be something other than bool some of the time.

Why then does the first program succeed at compile time?

The compiler has no idea that a method named "X.Cast" should be treated like a cast operator for the purposes of error detection, As far as the compiler is concerned, the Cast method is a method that takes an object in and returns a V for whatever type parameter is provided for V . When compiling the body of the ctor of D, the compiler has no idea whatsoever that some method, which probably isn't even in this program to begin with, is going to try to do a cast that is going to fail unless U happens to be bool.

The compiler simply has no basis upon which to treat the first version as an error, even though it most certainly is a deeply wrong program. You'll have to wait until runtime to find out that your program is wrong.

Now let's make a third version of your program:

class C<T> {}
class D<U>
{
    public C<U> value;
    public D()
    {
        this.value = (C<U>)(object)(new C<bool>());
    }
}

This succeeds at compile time, so let's ask:

Why does this succeed at compile time?

For the exact same reason that the first one succeeds at compile time. When you inserted the cast you effectively said that you wanted the newly constructed C<bool> to be treated as an object, and so for the rest of the analysis of this expression, that expression is considered to be of type object, and not the more specific type C<bool> .

So then why is it legal to cast object to C<U> in this case? Or for that matter, to V in the first case?

It is legal to cast object to V because V could be the type of the object, a base type of the object or an interface implemented by the object, so the compiler allows the conversion because it figures there are a lot of ways it could possibly succeed.

Basically, it is legal to cast object to anything that you could convert to object . You cannot cast object to a pointer type, for instance, because no pointer type can be cast to object . But everything else is fair game.

By making the cast to object first, you are removing information from the purview of the compiler; you are saying "ignore the fact that you know this is always C<bool> for the purposes of error detection.

In your second example, you're trying to convert NamedValue<bool> to NamedValue<TValue> -- this won't work, because the conversion has to be valid for any type argument. You can't convert NamedValue<bool> to NamedValue<int> or NamedValue<string> or NamedValue<AnythingElseOtherThanBool> .

One solution is to make NamedValueSource<TValue> abstract, as well as its GetNamedValues() method, and then create a class BooleanNamedValueSource: NamedValueSource<bool> class to use in your test.

In the linq case, the cast is not being done by the compiler; the cast occurs in a method that has already been compiled. All the compiler knows is that it is calling a method that takes IEnumerable<bool> and returns IEnumerable<TValue> . The specifics of that conversion are entirely invisible to the compiler.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM