Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How is "as" operator translated when the right-side operand is generic?

I have just posted an answer to this question but I'm not entirely convinced of my answer.There are two things I'm wondering, consider this code:

class Foo<T>
{ 
    void SomeMethod()
    {
        string str = "foo";
        Foo<T> f = str as Foo<T>;
    }
}

According to C# Specification 5.0, there are two different kinds of conversion of as operator.

If the compile-time type of E is not dynamic, the operation E as T produces the same result as

E is T ? (T)(E) : (T)null

If the compile-time type of E is dynamic, unlike the cast operator the as operator is not dynamically bound (§7.2.2). Therefore the expansion in this case is:

E is T ? (T)(object)(E) : (T)null

Since, this is invalid because of (Foo<T>)str

str is Foo<T> ? (Foo<T>)str : (Foo<T>)null;

I thought it should be translated as:

str is Foo<T> ? (Foo<T>)(object)str : (Foo<T>)null;

But the spec says this only happens when the type of E is dynamic.

So my questions are:

  1. Is the compiler translating this expression to a code that is normally invalid?
  2. When the type of E is dynamic why first it casts E to object then T while the (T)E is completely valid?
like image 350
Selman Genç Avatar asked Jan 26 '15 13:01

Selman Genç


People also ask

What type of operator is -=?

-= (Subtraction Assignment) – To store the difference of both the operands to the left side operand. *= (Multiplication Assignment) – To store the product of both the operands to the left side operand.

What does -> mean in Kotlin?

The -> is a separator. It is special symbol used to separate code with different purposes. It can be used to: Separate the parameters and body of a lambda expression val sum = { x: Int, y: Int -> x + y }

When an operator is used in between two operands it is called as?

Binary operators require two operands. The arithmetic operators are the most familiar examples of binary operators.

What is operator How is it related with operand?

The operators indicate what action or operation to perform. The operands indicate what items to apply the action to. An operand can be any of the following kinds of data items: Constant.


1 Answers

Is the compiler translating this expression to a code that is normally invalid?

After staring at the spec for about an hour, I'm starting to convince myself that this is simply an edge-case which was overlooked in the specification. Note that this is merely a way for the C# language composers to express the as operator with the semantics of the is operator.

The compiler doesn't actually convert the as operator to a ternary operator with an is. It will emit an IL call to isinst, both for as and is:

IL_0000: nop
IL_0001: ldstr "foo"
IL_0006: stloc.0
IL_0007: ldloc.0
IL_0008: isinst class ConsoleApplication2.Foo`1<!T>
IL_000d: stloc.1
IL_000e: ret

Looking at the compiled DLL, the as operator remains untouched.

When the type of E is dynamic why first it casts E to object then T while the (T)E is completely valid?

This is described in the fine-print of the specification:

If the compile-time type of E is dynamic, unlike the cast operator the as operator is not dynamically bound (§7.2.2). Therefore the expansion in this case is:

E is T ? (T)(object)(E) : (T)null

The cast to object is needed to make the use of as possible with dynamic objects. as is a compile-time operation while dynamic objects are bound only at run-time.

The compiler actually treats dynamic type objects as type object to begin with:

class Foo<T> 
{
    public void SomeMethod() 
    {
        dynamic str = "foo";
        Foo<T> f = str as Foo<T>;
    }
}

str is actually treated as object to begin with:

.class private auto ansi beforefieldinit Foo`1<T>
    extends [mscorlib]System.Object
{
    // Methods
    .method public hidebysig 
        instance void SomeMethod () cil managed 
    {
        // Method begins at RVA 0x2050
        // Code size 15 (0xf)
        .maxstack 1
        .locals init (
            [0] object,
            [1] class Foo`1<!T>
        )

        IL_0000: nop
        IL_0001: ldstr "foo"
        IL_0006: stloc.0
        IL_0007: ldloc.0
        IL_0008: isinst class Foo`1<!T>
        IL_000d: stloc.1
        IL_000e: ret
    } // end of method Foo`1::SomeMethod
}

Edit:

After talking to Vladimir Reshetnikov from the Managed Languages Team, he explains what the semantic of the representation from the "as operator" to "cast operator" actually tries to convay:

I agree, there is some imprecise language in the spec too. It says 'as' operator is always applicable if an open type involved, but then describes its evaluation in terms of casts, that might be not valid in some cases. It should say that casts in the expansion do not represent normal C# cast operator but just represent conversions that are permitted in 'as' operators. I'll take a note to fix it. Thanks!

like image 50
Yuval Itzchakov Avatar answered Oct 29 '22 12:10

Yuval Itzchakov