Why does the C # compiler allow translation 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 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 fine (the assertion passes):

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

      

Now, here's the fun part. If I try to cast in 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 it NamedValueSource<TValue>

compile on errors NamedValueSourceFail<TValue>

? In particular, why can I do the listing using Linq but not with the nice ol brackets?

Edit

If it's not entirely clear from the comment thread of the accepted answer, I just need to convert to first object

, then I will be allowed to use NamedValue<TValue>

. Most likely, the Linq method Cast

works behind the scenes.

+3


source to share


2 answers


In your second example, you are trying to convert NamedValue<bool>

to NamedValue<TValue>

- it won't work because the conversion must be valid for any type argument. You cannot convert NamedValue<bool>

to NamedValue<int>

or NamedValue<string>

or NamedValue<AnythingElseOtherThanBool>

.

One solution is to create an abstract text NamedValueSource<TValue>

as well as its method GetNamedValues()

, and then create a class BooleanNamedValueSource : NamedValueSource<bool>

for use in your test.



In the case of linq, the casting is not done by the compiler; the casting happens in a method that has already been compiled. The whole compiler knows that it is calling a method that takes IEnumerable<bool>

and returns IEnumerable<TValue>

. The specifics of this conversion are completely invisible to the compiler.

+8


source


UPDATE . This question was a topic on my blog on July 10, 2012 ; thanks for the great question!


Let me greatly simplify your complex 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>());
    }
}

      

Okay, now let's ask a few questions.

Why does the second program crash 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 can happen is U

always with bool, and so this program is almost certainly wrong! The compiler assumes it U

will be something other than bool for a while.

Why then is the first program executed at compile time?

The compiler doesn't know that a method named "X.Cast" should be treated as a translator to catch errors! As far as the compiler is concerned, a method Cast

is a method that takes an object and returns V

for any type parameter for V

. When compiling the body of ctor D, the compiler has no idea that some method, which is probably not even in this program to begin with, will try to cast, which will fail if U

bool happens.



The compiler simply has no reason to treat the first version as a bug, although it is certainly a very wrong program. You will need to wait for the execution time to find out that your program is wrong.

Now, let's make the 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 ask the question:

Why does this succeed at compile time?

For the same reason that the first one succeeded at compile time. When you inserted the cast, you effectively said that you want the newly constructed one to C<bool>

be treated as an object, and so for the rest of the parsing of that expression, that expression is considered an object of a type, not much more a concrete type C<bool>

.

So why, in this case, is it legal to cast the object in C<U>

? Or, for that matter, before V

in the first case?

It is legal to expose an object V

because it V

could be the type of the object, the underlying type of the object, or the interface implemented by the object, so the compiler allows the conversion as it shows that there are many ways that this could be successful.

Basically, it's legal to use object

for anything you could convert to object

. You cannot use object

for a pointer type, for example, because no pointer type can be added to object

. But everything else is fair play.

By first translating object

, you remove the information from the compiler's field of view; you say, “ignore the fact that you know it’s always C<bool>

for error detection.

+12


source







All Articles