C# equivalent to TryCast

D

Dustin Campbell

System.Web.UI.HtmlControls.HtmlHead header;
header = this.Page.Header as System.Web.UI.HtmlControls.HtmlHead;
if (header == null)
// cast failed...
else
// cast succeeded...

Best Regards,
Dustin Campbell
Developer Express Inc.
 
N

nick.fletcher

You can also use the "is" keyword

System.Web.UI.HtmlControls.HtmlHead header;

if (this.PageHeader is System.Web.UI.HtmlControls.HtmlHead)
{
header = (this.PageHeader as
System.Web.UI.HtmlControls.HtmlHead);
}
 
W

Wiebe Tijsma

This works fine as well, but I think the previous example has preference
because it will only cast the object once.

Wiebe Tijsma
 
G

Gabriel Lozano-Morán

I am just curious on why you want to cast at all?

Gabriel Lozano-Morán
 
C

clintonG

Can somebody explain why this cast and this form of cast is used?

// An object named header is derived (?) from the class HtmlHead
System.Web.UI.HtmlControls.HtmlHead header;

// Using the is keyword this instance of the PageHeader object is typed
// as an HtmlHead object allowing the instance of this.PageHeader object
// to "borrow" properties of an HtmlHead object
this.PageHeader is System.Web.UI.HtmlControls.HtmlHead

I read the "is a" and the "has a" relationship but I don't understand
the "why a" and this is where I burn out.


<%= Clinton Gallagher
 
C

clintonG

I found the code in VB while trying to learn what options were available to
me to write into the head of the page when using Themes. I need to control
writing linked style sheets *after* the link element generated by the Theme.

<%= Clinton Gallagher
 
N

nick.fletcher

In fact - the compiled msil is exactly the same but the example which
doesnt use "is" makes one extra read and write to the stack (tho not
being an expert in reading msil I couldnt tell you why)

try it :)
 
D

Dustin Campbell

In fact - the compiled msil is exactly the same but the example which
doesnt use "is" makes one extra read and write to the stack (tho not
being an expert in reading msil I couldnt tell you why)

try it :)

Actually that's not true at all. See http://www.boyet.com/Articles/DoubleCastingAntiPattern.html
for details. I compiled the example code at that blog entry and got the following
results:

class Program
{
class Foo
{
public int Length { get { return 0; } }
}

static int DoubleCast(object obj)
{
if (obj is Foo)
return ((Foo)obj).Length;

return -1;
}
static int SingleCast(object obj)
{
Foo foo = obj as Foo;
if (foo != null)
return foo.Length;

return -1;
}

static void Main(string[] args) { }
}

DoubleCast compiles to this IL:

..method private hidebysig static int32 DoubleCast(object obj) cil managed
{
.maxstack 8
L_0000: ldarg.0
L_0001: isinst CastTest.Program/Foo
L_0006: brfalse.s L_0014
L_0008: ldarg.0
L_0009: castclass CastTest.Program/Foo
L_000e: callvirt instance int32 CastTest.Program/Foo::get_Length()
L_0013: ret
L_0014: ldc.i4.m1
L_0015: ret
}

and SingleCast compiles to this:

..method private hidebysig static int32 SingleCast(object obj) cil managed
{
.maxstack 1
.locals init (
[0] CastTest.Program/Foo foo)
L_0000: ldarg.0
L_0001: isinst CastTest.Program/Foo
L_0006: stloc.0
L_0007: ldloc.0
L_0008: brfalse.s L_0011
L_000a: ldloc.0
L_000b: callvirt instance int32 CastTest.Program/Foo::get_Length()
L_0010: ret
L_0011: ldc.i4.m1
L_0012: ret
}

The differeince is the additional "castclass" in DoubleCast at L_0012. This
actually results in two "castclass" operations if you consider the definition
of the "isinst" instruction that appears L_0002:

"isinst <token> (0x75). Check to see whether the object reference on the
stack is an instance of the class specified by <token>. <token> must be a
valid TypeDef, TypeRef, or TypeSpec token. This instruction takes the object
reference from the stack and pushes the result on the stack. If the check
succeeds, the result is an object reference, **as if castclass had been invoked**;
otherwise, the result is a null reference, as if ldnull had been invoked."
- Expert .NET 2.0 IL Assembler by Serge Lidin, pg. 287 (emphasis mine)

So, in the first example, the IL is not all that optimal because the result
of the "isinst" instruction is compared against null and thrown away -- only
to be recalculated later from the parameter by the "castclass" instruction.
In the second example, the result of the *isinst" instruction is copied to
a local so there isn't any need for "castclass".

Best Regards,
Dustin Campbell
Developer Express Inc.
 
D

Dave Sexton

Hi Dustin,

Interesting stuff.

If you run VS 2005 Code Analysis on the sample code you'll see a warning
about double-casting. It's one of the more annoying warnings produced, IMO.

"Do not cast unnecessarily (CA1800)"
http://msdn2.microsoft.com/en-us/library/ms182271(vs.80).aspx

Personally, I prefer the code in DoubleCast and I'm not really worried about
any negative impact it may have on performance, normally.

(On a side note, I'd just like to vent a bit: MSDN is terribly slow right
now and it's driving me crazy :)

--
Dave Sexton

Dustin Campbell said:
In fact - the compiled msil is exactly the same but the example which
doesnt use "is" makes one extra read and write to the stack (tho not
being an expert in reading msil I couldnt tell you why)

try it :)

Actually that's not true at all. See
http://www.boyet.com/Articles/DoubleCastingAntiPattern.html for details. I
compiled the example code at that blog entry and got the following
results:

class Program
{
class Foo
{
public int Length { get { return 0; } }
}

static int DoubleCast(object obj)
{
if (obj is Foo)
return ((Foo)obj).Length;

return -1;
}
static int SingleCast(object obj)
{
Foo foo = obj as Foo;
if (foo != null)
return foo.Length;

return -1;
}

static void Main(string[] args) { }
}

DoubleCast compiles to this IL:

.method private hidebysig static int32 DoubleCast(object obj) cil managed
{
.maxstack 8
L_0000: ldarg.0 L_0001: isinst CastTest.Program/Foo
L_0006: brfalse.s L_0014
L_0008: ldarg.0 L_0009: castclass CastTest.Program/Foo
L_000e: callvirt instance int32 CastTest.Program/Foo::get_Length()
L_0013: ret L_0014: ldc.i4.m1 L_0015: ret }

and SingleCast compiles to this:

.method private hidebysig static int32 SingleCast(object obj) cil managed
{
.maxstack 1
.locals init (
[0] CastTest.Program/Foo foo)
L_0000: ldarg.0 L_0001: isinst CastTest.Program/Foo
L_0006: stloc.0 L_0007: ldloc.0 L_0008: brfalse.s L_0011
L_000a: ldloc.0 L_000b: callvirt instance int32
CastTest.Program/Foo::get_Length()
L_0010: ret L_0011: ldc.i4.m1 L_0012: ret }

The differeince is the additional "castclass" in DoubleCast at L_0012.
This actually results in two "castclass" operations if you consider the
definition of the "isinst" instruction that appears L_0002:

"isinst <token> (0x75). Check to see whether the object reference on the
stack is an instance of the class specified by <token>. <token> must be a
valid TypeDef, TypeRef, or TypeSpec token. This instruction takes the
object reference from the stack and pushes the result on the stack. If the
check succeeds, the result is an object reference, **as if castclass had
been invoked**; otherwise, the result is a null reference, as if ldnull
had been invoked." - Expert .NET 2.0 IL Assembler by Serge Lidin, pg. 287
(emphasis mine)

So, in the first example, the IL is not all that optimal because the
result of the "isinst" instruction is compared against null and thrown
away -- only to be recalculated later from the parameter by the
"castclass" instruction. In the second example, the result of the *isinst"
instruction is copied to a local so there isn't any need for "castclass".

Best Regards,
Dustin Campbell
Developer Express Inc.
 
D

Dustin Campbell

"Do not cast unnecessarily (CA1800)"
http://msdn2.microsoft.com/en-us/library/ms182271(vs.80).aspx
Personally, I prefer the code in DoubleCast and I'm not really worried
about any negative impact it may have on performance, normally.

Just as long as you know that the problem is there. I mean, I haven't stopped
using foreach just because it's inefficient in comparisan to say Array.ForEach,
List.ForEach or a hard-coded for or while loop.

Best Regards,
Dustin Campbell
Developer Express Inc.
 
D

Dave Sexton

Hi Dustin,

Agreed.

--
Dave Sexton

Dustin Campbell said:
Just as long as you know that the problem is there. I mean, I haven't
stopped using foreach just because it's inefficient in comparisan to say
Array.ForEach, List.ForEach or a hard-coded for or while loop.

Best Regards,
Dustin Campbell
Developer Express Inc.
 
D

Dave Sexton

Hi,
Can somebody explain why this cast and this form of cast is used?

"is" is a binary operator used to check whether a variable "is" of the
specified type or a derived-type. It's commonly used as a predicate in an
"if" statement. "is" does not perform a cast that can be assigned to a
variable.

"as" is a binary operator used to coerce a variable into the specified
reference-type or null if the variable is null or the instance is not of the
specified reference-type or a derived-type. "as" performs a cast that can
be assigned to a variable.

Without the use of "is" and "as" you'd have to use reflection or attempt an
explicit cast, catching InvalidCastException.
// An object named header is derived (?) from the class HtmlHead
System.Web.UI.HtmlControls.HtmlHead header;

In your example a variable named header is typed as HtmlHead. You could
even say that, "header is an instance of HtmlHead", unless of course it's a
null reference, but "derived" is not the appropriate word to use here.
// Using the is keyword this instance of the PageHeader object is typed
// as an HtmlHead object allowing the instance of this.PageHeader object
// to "borrow" properties of an HtmlHead object
this.PageHeader is System.Web.UI.HtmlControls.HtmlHead

The "is" operator returns either true or false. It's used as a comparison,
not for assignment.

In your example, if the PageHeader property returns an instance of an object
that is of the type, "HtmlHead" or a derived type, then the entire "is"
expression will return true, otherwise false:

bool isHtmlHead = this.PageHeader is HtmlHead;

The "as" operator returns the result of the coercion. When the argument
being tested is not null and is of the specified type or a derived type, a
non-null reference will be returned, otherwise null will be returned:

HtmlHead header = this.PageHeader as HtmlHead;

if (header != null)
Trace.WriteLine("PageHeader is an instance of HtmlHead or a derived
type");
else
Trace.WriteLine("PageHeader is null or not an instance of HtmlHead or a
derived type");
I read the "is a" and the "has a" relationship but I don't understand
the "why a" and this is where I burn out.

You got me here - care to clarify this statement?
 
C

clintonG

Dave Sexton said:
Hi,


"is" is a binary operator used to check whether a variable "is" of the
specified type or a derived-type. It's commonly used as a predicate in an
"if" statement. "is" does not perform a cast that can be assigned to a
variable.

"as" is a binary operator used to coerce a variable into the specified
reference-type or null if the variable is null or the instance is not of
the specified reference-type or a derived-type. "as" performs a cast that
can be assigned to a variable.

Without the use of "is" and "as" you'd have to use reflection or attempt
an explicit cast, catching InvalidCastException.


In your example a variable named header is typed as HtmlHead. You could
even say that, "header is an instance of HtmlHead", unless of course it's
a null reference, but "derived" is not the appropriate word to use here.


The "is" operator returns either true or false. It's used as a
comparison, not for assignment.

In your example, if the PageHeader property returns an instance of an
object that is of the type, "HtmlHead" or a derived type, then the entire
"is" expression will return true, otherwise false:

bool isHtmlHead = this.PageHeader is HtmlHead;

The "as" operator returns the result of the coercion. When the argument
being tested is not null and is of the specified type or a derived type, a
non-null reference will be returned, otherwise null will be returned:

HtmlHead header = this.PageHeader as HtmlHead;

if (header != null)
Trace.WriteLine("PageHeader is an instance of HtmlHead or a derived
type");
else
Trace.WriteLine("PageHeader is null or not an instance of HtmlHead or a
derived type");


You got me here - care to clarify this statement?

Very well said Dave thank you, your sage advice will help me understand
documentation.
Pay no attention to the "why a" it was a facetious reference to "why" this
code snippet should use "is" or "as."

<%= Clinton
 
N

nick.fletcher

Hi Dustin

Youve got me interested now. Ive redone the code which I tested
yesterday which is essentially as follows

double cast
static void Main(string[] args)
{
B myclass = new B();

if (myclass is A)
(myclass as A).GetMyString();
}

single cast
class Program
{
static void Main(string[] args)
{
B myclass = new B();

A a = myclass as A;
if (a != null)
a.GetMyString();
}

Where B is a simple class which derives from A and GetMyString is a
public method on A

These compile to

single cast
..method private hidebysig static void Main(string[] args) cil managed
{
.entrypoint
// Code size 25 (0x19)
.maxstack 2
.locals init ([0] class ConsoleApplication2.B myclass,
[1] class ConsoleApplication2.A a,
[2] bool CS$4$0000)
IL_0000: nop
IL_0001: newobj instance void ConsoleApplication2.B::.ctor()
IL_0006: stloc.0
IL_0007: ldloc.0
IL_0008: stloc.1
IL_0009: ldloc.1
IL_000a: ldnull
IL_000b: ceq
IL_000d: stloc.2
IL_000e: ldloc.2
IL_000f: brtrue.s IL_0018
IL_0011: ldloc.1
IL_0012: callvirt instance string
ConsoleApplication2.A::GetMyString()
IL_0017: pop
IL_0018: ret
} // end of method Program::Main

double cast

..method private hidebysig static void Main(string[] args) cil managed
{
.entrypoint
// Code size 23 (0x17)
.maxstack 2
.locals init ([0] class ConsoleApplication2.B myclass,
[1] bool CS$4$0000)
IL_0000: nop
IL_0001: newobj instance void ConsoleApplication2.B::.ctor()
IL_0006: stloc.0
IL_0007: ldloc.0
IL_0008: ldnull
IL_0009: ceq
IL_000b: stloc.1
IL_000c: ldloc.1
IL_000d: brtrue.s IL_0016
IL_000f: ldloc.0
IL_0010: callvirt instance string
ConsoleApplication2.A::GetMyString()
IL_0015: pop
IL_0016: ret
} // end of method Program::Main

which is obviously contradictory to your findings.

Have I made some fundamental mistake here?

In fact - the compiled msil is exactly the same but the example which
doesnt use "is" makes one extra read and write to the stack (tho not
being an expert in reading msil I couldnt tell you why)
try it :)Actually that's not true at all. Seehttp://www.boyet.com/Articles/DoubleCastingAntiPattern.html
for details. I compiled the example code at that blog entry and got the following
results:

class Program
{
class Foo
{
public int Length { get { return 0; } }
}

static int DoubleCast(object obj)
{
if (obj is Foo)
return ((Foo)obj).Length;

return -1;
}
static int SingleCast(object obj)
{
Foo foo = obj as Foo;
if (foo != null)
return foo.Length;

return -1;
}

static void Main(string[] args) { }

}DoubleCast compiles to this IL:

.method private hidebysig static int32 DoubleCast(object obj) cil managed
{
.maxstack 8
L_0000: ldarg.0
L_0001: isinst CastTest.Program/Foo
L_0006: brfalse.s L_0014
L_0008: ldarg.0
L_0009: castclass CastTest.Program/Foo
L_000e: callvirt instance int32 CastTest.Program/Foo::get_Length()
L_0013: ret
L_0014: ldc.i4.m1
L_0015: ret

}and SingleCast compiles to this:

.method private hidebysig static int32 SingleCast(object obj) cil managed
{
.maxstack 1
.locals init (
[0] CastTest.Program/Foo foo)
L_0000: ldarg.0
L_0001: isinst CastTest.Program/Foo
L_0006: stloc.0
L_0007: ldloc.0
L_0008: brfalse.s L_0011
L_000a: ldloc.0
L_000b: callvirt instance int32 CastTest.Program/Foo::get_Length()
L_0010: ret
L_0011: ldc.i4.m1
L_0012: ret

}The differeince is the additional "castclass" in DoubleCast at L_0012. This
actually results in two "castclass" operations if you consider the definition
of the "isinst" instruction that appears L_0002:

"isinst <token> (0x75). Check to see whether the object reference on the
stack is an instance of the class specified by <token>. <token> must be a
valid TypeDef, TypeRef, or TypeSpec token. This instruction takes the object
reference from the stack and pushes the result on the stack. If the check
succeeds, the result is an object reference, **as if castclass had been invoked**;
otherwise, the result is a null reference, as if ldnull had been invoked."
- Expert .NET 2.0 IL Assembler by Serge Lidin, pg. 287 (emphasis mine)

So, in the first example, the IL is not all that optimal because the result
of the "isinst" instruction is compared against null and thrown away -- only
to be recalculated later from the parameter by the "castclass" instruction.
In the second example, the result of the *isinst" instruction is copied to
a local so there isn't any need for "castclass".

Best Regards,
Dustin Campbell
Developer Express Inc.
 
J

Jon Skeet [C# MVP]

Youve got me interested now. Ive redone the code which I tested
yesterday which is essentially as follows

double cast
static void Main(string[] args)
{
B myclass = new B();

if (myclass is A)
(myclass as A).GetMyString();
}

single cast
class Program
{
static void Main(string[] args)
{
B myclass = new B();

A a = myclass as A;
if (a != null)
a.GetMyString();
}

Where B is a simple class which derives from A and GetMyString is a
public method on A

<snip>

In your code, the compiler can work out that an instance of B is
*always* an instance of A, so it doesn't bother with the class.

A better test is:

using System;

class Test
{
static void Main(string[] args)
{
// Dummy method
}

static void DoubleCast (object o)
{
if (o is string)
{
Console.WriteLine ((String) o);
}
}

static void SingleCast (object o)
{
string s = o as string;
if (s != null)
{
Console.WriteLine (s);
}
}
}

Now, I believe that in 1.1 that would use isinst for both methods. In
2.0, it uses the castclass op code for the second cast in DoubleCast.
Couldn't say why - I don't know if castclass is new for 2.0...
 
D

Dustin Campbell

Now, I believe that in 1.1 that would use isinst for both methods. In
2.0, it uses the castclass op code for the second cast in DoubleCast.

That's correct.
Couldn't say why - I don't know if castclass is new for 2.0...

castclass was in 1.0. The difference is this: castclass will throw an InvalidCastException.

Best Regards,
Dustin Campbell
Developer Express Inc.
 
D

Dustin Campbell

Now, I believe that in 1.1 that would use isinst for both methods. In
That's correct.

Actually, after doing a few more tests, I've seen cases where 2.0 also produces
two isinst opcodes. I suppose it's an optimization for specific cases to
use castclass.

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

Top