Based on your instructions, I created this code:
namespace DoubleCastTTest
{
public class A
{
public string GetMyString()
{
return "Text";
}
}
public class B: A
{
}
public class DoubleCastTest
{
static void DoubleCast()
{
B myclass = new B();
if (myclass is A)
(myclass as A).GetMyString();
}
}
public class SingleCastTest
{
static void SingleCast()
{
B myclass = new B();
A a = myclass as A;
if (a != null)
a.GetMyString();
}
}
}
Have I made some fundamental mistake here?
Your test is a bit flawed because the compiler has absolutely certainty that
B can be down-cast to A. The compiler knows that this is implicitly-convertible.
In fact, you don't need the "is" or "as" operators at all in your code. You
could write this:
B myclass = new B();
A a = myclass;
a.GetMyString();
Or this:
A a = new B();
a.GetMyString();
In addition, you should build in Release mode to see the real production
IL with optimizations. It clears out a lot of noise.
When compiled, I get this IL:
..method private hidebysig static void DoubleCast() cil managed
{
.maxstack 1
.locals init (
[0] DoubleCastTTest.B myclass)
L_0000: newobj instance void DoubleCastTTest.B::.ctor()
L_0005: stloc.0
L_0006: ldloc.0
L_0007: brfalse.s L_0010
L_0009: ldloc.0
L_000a: callvirt instance string DoubleCastTTest.A::GetMyString()
L_000f: pop
L_0010: ret
}
..method private hidebysig static void SingleCast() cil managed
{
.maxstack 1
.locals init (
[0] DoubleCastTTest.B myclass,
[1] DoubleCastTTest.A a)
L_0000: newobj instance void DoubleCastTTest.B::.ctor()
L_0005: stloc.0
L_0006: ldloc.0
L_0007: stloc.1
L_0008: ldloc.1
L_0009: brfalse.s L_0012
L_000b: ldloc.1
L_000c: callvirt instance string DoubleCastTTest.A::GetMyString()
L_0011: pop
L_0012: ret
}
Now, in SingleCast, you had to declare a new local variable to store the
reference returned by "myclass as A". That is why there is more code. In
DoubleCast, the compiler knows that B is implicitly-convertiable to A so
it reuses the same local variable. In fact, because of these implicit-conversions,
the C# compiler could be further optimized to make SingleCast look exactly
like DoubleCast.
OK, so, you've tested the best case scenario -- the one in which there is
absolutely no uncertainty. So, let's throw a little uncertainty into the mix:
namespace DoubleCastTTest
{
public class A
{
public string GetMyString()
{
return "Text";
}
}
public class B: A
{
}
public class DoubleCastTest
{
static void DoubleCast(A a)
{
if (a is B)
(a as B).GetMyString();
}
}
public class SingleCastTest
{
static void SingleCast(A a)
{
B b = a as B;
if (b != null)
b.GetMyString();
}
}
}
In this case, the compiler knows nothing about the instance of A that is
being passed to it so it can't do any optimizations when up-casting to B.
And, here's the IL:
..method private hidebysig static void DoubleCast(DoubleCastTTest.A a) cil
managed
{
.maxstack 8
L_0000: ldarg.0
L_0001: isinst DoubleCastTTest.B
L_0006: brfalse.s L_0014
L_0008: ldarg.0
L_0009: isinst DoubleCastTTest.B
L_000e: callvirt instance string DoubleCastTTest.A::GetMyString()
L_0013: pop
L_0014: ret
}
..method private hidebysig static void SingleCast(DoubleCastTTest.A a) cil
managed
{
.maxstack 1
.locals init (
[0] DoubleCastTTest.B b)
L_0000: ldarg.0
L_0001: isinst DoubleCastTTest.B
L_0006: stloc.0
L_0007: ldloc.0
L_0008: brfalse.s L_0011
L_000a: ldloc.0
L_000b: callvirt instance string DoubleCastTTest.A::GetMyString()
L_0010: pop
L_0011: ret
}
Now the double-cast shows its true colors.
Best Regards,
Dustin Campbell
Developer Express Inc.