Yes, this behavior is by design. Don't forget that "DynamicObject" is just a useful helper class which is designed to make it easy to implement objects with dynamic behavior from C# -- it's not actually a core implementation class for dynamic behavior.
The source code for DynamicObject can be downloaded either from this (DLR) site or as part of IronPython. Reading it should be instructive. CallMethodWithResult is the single method most responsible for the behavior in question. There's one particular comment
which deserves your attention:
// Now, call fallback again using our new MO as the error
// When we do this, one of two things can happen:
// 1. Binding will succeed, and it will ignore our call to
// the dynamic method, OR
// 2. Binding will fail, and it will use the MO we created
The "fallback" in this case refers to the default behavior as defined by the language using your object. So basically, the C# runtime binder always gets first crack at dispatch for DynamicObject. Only if it fails will the dynamic behavior be invoked.
If you don't waht this behavior -- if you want full control over dispatch -- you would need to define your own DynamicMetaObject and return that from your IDynamicMetaObjectProvider implementation.
But the simplest way to prevent the behavior you describe is simply to make the implementation of "Foo" private. Then the C# runtime binder won't see it, and your implementation of TryInvokeMember will always be called.