C# equivalent to TryCast

  • Thread starter Thread starter clintonG
  • Start date Start date
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.
 
Hi Dustin,

Thanks very much for taking the time to go through that. Most helpful

Cheers

Nick

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.
 

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Back
Top