Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

What's the C# equivalent to C++'s dynamic_cast?

Tags:

c++

c#

casting

This C++ code checks if o is a Node * and if so, calls a method on d.

if (Node * d = dynamic_cast<Node *>(o)) d->do_it();

What's the shortest and/or most efficient way to write the equivalent in C#?

like image 629
Mark Beckwith Avatar asked Feb 16 '12 17:02

Mark Beckwith


2 Answers

Assuming that Node is a class then do the following

Node d = o as Node;
if (d != null) {
  d.do_it();
}

If instead it's a struct then try this

if (o is Node) {
  ((Node)o).do_it();
}
like image 54
JaredPar Avatar answered Oct 20 '22 00:10

JaredPar


As of C# 6 (July 2015), assuming Node is a class (or Nullable<T>, string, etc), using your example where you

  1. check if o is a Node (not actually the same as converting o to a Node--see note about casting vs converting below)
  2. if so, call do_it()
  3. immediately discard the cast value

you can use the null-conditional operator:

(o as Node)?.do_it();

This syntax also handles the case where o is, in fact, declared as Node, but happens to be null.

If you want to keep the cast variable, as of C# 7 (March 2017), you can run:

if (o is Node node)
{
    node.do_it();
}

The variable node at this point is in the scope outside of the if statement, equivalent to:

Node node = o as Node;
if (node != null) 
{
    node.do_it();
}

So, if you want to only continue the execution if o is a Node, you can write:

if (!(o is Node node)) 
{
    return; // or continue, etc
}

node.do_it();
// ...

Note: The is keyword will always return false if o is null, even if you directly specify the type and then ask if that variable is that type.

string foo = null;
if (foo is string)
{
    // never gets here
    Console.WriteLine(foo);
}

Casting vs Converting

The is and as keywords do the same as C++'s dynamic_cast<T>: they will check against the specified type, subtype, or interface, but will not actually change the value in memory. They simply tell the compiler which methods should be available on the variable.

There's a misnomer amongst C# users where we use the words "cast" and "convert" interchangeably. This likely stems from the fact that we often know that a base type variable is always going to be a subtype, and so we use the convert syntax when puritanically we should be using the cast syntax:

void Foo(MyBaseType value)
{
    // let's assume `value` will always be a MySubType
    MySubType subTypeValue = (MySubType)value;
}

This syntax will throw at runtime if value is not, in fact, MySubType.

Converting differs from casting in that the value in memory may change. Consider int and double.

void Foo()
{
    // implicit converting
    int x = 1;
    double y = x;

    // explicit converting
    y = 1.5;
    x = (int)y;
}

In each of these cases, the literal value stored in memory changes format. ints can always be represented by a double--there will never be a loss in data--and so there is a defined implicit operator that will manipulate the data in memory into the new format. doubles, being floating point values and having a range larger than ints, cannot guarantee no loss in data, so C# requires an explicit conversion (usually termed "explicit cast") via the explicit operator to indicate to the compiler that we're okay with losing data.

With classes, we can define our own implicit and explicit operators which will manipulate the data whatever way we see fit. This is where the misnomer between convert and cast gets messy.

using System;

public class Program
{
    public static void Main()
    {
        Foo foo = new Foo();
        Bar bar = (Bar)foo;

        // writes "1" (or would, if the program compiled)
        Console.WriteLine(bar);

        // throws compilation error: "Cannot convert type `Foo' to `Bar' via built-in conversion"
        bar = foo as Bar;

        // note: the same would happen if `foo` was type int? and `bar` was type `double?`
        //       even though double? can be converted to int?
    }
}

class Foo
{
    public readonly int Value = 1;

    public static explicit operator Bar(Foo foo)
    {
        return new Bar(foo.Value.ToString());
    }
}

class Bar
{
    public readonly string Value;

    public Bar(string value)
    {
        Value = value;
    }
}
like image 27
dx_over_dt Avatar answered Oct 19 '22 23:10

dx_over_dt