I have an application where I am performing an operation on a series of elements and the exact nature of the operation depends on the type of the element being operated upon
Are there any gotchas with the dynamic dispatching that I haven't considered? I believe this is equivalent to performing a series of if (x is TypeA) Do((TypeA)x) else..., but I could be wrong.
The main gotcha would be if a type implements more than one interface in your visitor pattern - the compiler will probably pick the one you want, but it may not be the same choice you'd make if you use if (x is TypeA)/else if (x is TypeB) logic, as you'd control the order the checks occur.
Is this actually cleaner and easier to understand than a long if/elseif method?
I personally think so. This provides a very clean, fairly decently performing dispatch determined by the runtime type, and "just works." Hard to beat simple, short, clean code. Just make sure to (potentially) handle the case where you get a runtime error from the wrong type being passed in.