Home > .NET, Fun > C# Teaser – The Tricky Ternary Teaser?

C# Teaser – The Tricky Ternary Teaser?

Today we came across a little quirk that is worth sharing. Big thanks to Bob Flanders and Jeff Schoolcraft for help in figuring out the quirk.

The Rules:

  1. Prize (a Thycotic keyring light) to be mailed to the firstcomment with the correct answer(s) on this blog post withvalid contact information.
  2. My definition of the problem is the correct one. 🙂 Other answersmight be right butwon’t win the prize.
  3. I will post the answer within a few days.

using System.Web.UI;

using System.Web.UI.WebControls;

namespace Teasers

{

public class TernaryTeaser

{

private object GetValue(Control o)

{

return (o is TextBox && ((TextBox)o).Text.Trim() != “”) ?

((TextBox) o).Text : System.DBNull.Value;

}

}

}

  1. What is the problem and what is the easiest way to fix it?
  2. Why do ternaries work this way – is there something at the compiler or ILlevel?

The first person to successfully answer part (1) gets a hearty pat on the back, the first person to explainpart (2) gets a Thycotic keyring light.

UPDATE: Thanks to Kirk and Joseph for spotting the logic error – not what I was looking for, just mysilly oversight(where’s a test when you need one!) – I have updated the code to make more sense.

Winner: Jonathan de Halleux

Answer & Discussion (click and drag your mouse to see the answer)

Answer:
The simplest fix I was looking for is casting the System.DBNull.Value to an object:

 return (o is TextBox && ((TextBox)o).Text.Trim() != "") ? 
((TextBox) o).Text : (object) System.DBNull.Value;

For a full explanation of the ternary/conditional operator, read the docs.
For the answer to part (2), see Jonathan de Halleux’s awesome detailed comment below.

Categories: .NET, Fun
  1. http://
    December 23, 2004 at 6:23 am

    Hey, btw, i never got my keyring light for the TDD teaser. 🙂

    breaking it down, the ternary is equivalent to this:

    if (o is TextBox)
    return System.DBNull.Value;
    else
    return ((TextBox)o).Text;

    but what you really want is:

    if (o is TextBox)
    return ((TextBox)o).Text;
    else
    return System.DBNull.Value;

    a better approach might be:

    TextBox tb = o as TextBox;
    return (tb == null) ? System.DBNull.Value : tb.Text;

    not a one-liner, but a bit cleaner to understand…

    i must not be grasping part (2)… seems like a logic error, not an error with ternaries, per se.

  2. Joseph Plant
    December 23, 2004 at 6:39 am

    1. The statement can be written as the equivalent
    – if (o is TextBox)
    {
    return DBNull.Value;
    }
    else
    {
    return ((TextBox) o).Text
    }

    – It can be seen that the if needs to be logically reversed. otherwise there will be an exception casting o to a TextBox.

    How to fix? swap the values…
    return (o is TextBox) ? ((TextBox) o).Text : DBNull.Value;

    2. Ternary’s work this way to allow null checks and validity checks in the first part, so it is possible to only execute one of the following statements.
    eg:
    return obj == null ? “” : obj.ToString();

    will work as the obj.ToString() is only executed when the obj != null.

    I am assuming this is done at the compiler level.

  3. Jonathan de Halleux
    December 23, 2004 at 8:04 am

    The ternary creates a local variable and stores the either true or false result in that local. Therefore, your example does not compile because there is not implicit conversion from DBNull.Value to string.

    You can see that by ildasm (or use Reflector) the GetValue method:

    .method private hidebysig instance object
    GetValue(class [System.Web]System.Web.UI.Control o) cil managed
    {
    // Code size 59 (0x3b)
    .maxstack 2
    .locals init ([0] object CS$1$0000)
    IL_0000: ldarg.1
    IL_0001: isinst [System.Web]System.Web.UI.WebControls.TextBox
    IL_0006: brfalse.s IL_0024

    IL_0008: ldarg.1
    IL_0009: castclass [System.Web]System.Web.UI.WebControls.TextBox
    IL_000e: callvirt instance string [System.Web]System.Web.UI.WebControls.TextBox::get_Text()
    IL_0013: callvirt instance string [mscorlib]System.String::Trim()
    IL_0018: ldstr “”
    IL_001d: call bool [mscorlib]System.String::op_Inequality(string,
    string)
    IL_0022: brtrue.s IL_002b

    IL_0024: ldsfld class [mscorlib]System.DBNull [mscorlib]System.DBNull::Value
    IL_0029: br.s IL_0036

    IL_002b: ldarg.1
    IL_002c: castclass [System.Web]System.Web.UI.WebControls.TextBox
    IL_0031: callvirt instance string [System.Web]System.Web.UI.WebControls.TextBox::get_Text()
    IL_0036: stloc.0
    IL_0037: br.s IL_0039

    IL_0039: ldloc.0
    IL_003a: ret
    } // end of method TernaryTeaser::GetValue

    IL_024 is the DBNull branch, it pushes DBNull on the stack. IL_002b to IL_0031 pushes Text trimmed on the stack. Now notice that they both store it in stloc.0 which is an object.

    Of course, I had to modify a bit the teaser in order to compile… but I’ll let you figure out.

  4. Michal Chaniewski
    December 23, 2004 at 8:06 am

    Both values alterantively returned from ternary have to be of the same type.

    The solution is to convert above expression to if…then:

    if(o is TextBox && ((TextBox)o).Text.Trim() != “”)
    {
    return ((TextBox) o).Text;
    }
    else
    {
    return System.DBNull.Value;
    }

  5. i/Noodle
    December 23, 2004 at 12:09 pm

    Jonathon an Michal are correct.
    Cast the TextBox.Text value to an object in the true part of the expression and it works.
    Was it your intent to return the untrimmed string as the result ? If not, then I think it should read … ?(object)(((TextBox) o).Text.Trim()) …

    And I guess the reason is just that the compiler determines the type of the result based on the type of the true part of the expression.

    i/Noodle

  6. Jonathan de Halleux
    December 23, 2004 at 4:18 pm

    Note that the example uses 2 casts and 1 is… you can do much better 9(0 cast and 1 is). Generally, you should avoid “hard” cast for ‘is’, store the result in a local and check for null.

    This will give a more efficient piece of code and easier to debug.

  1. No trackbacks yet.

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s

%d bloggers like this: