Just sharing something I discovered today that surprised me a little.
I use ScriptScopes extensively throughout my hosted code to try and
prevent interaction between separate scripts. In most cases, since I'm
dealing with user-provided keys, I don't cast them to dynamic but
access their contents through GetVariable() and SetVariable().
Dynamic lookup is still available though, and appears to be applied
partway through overload resolution:
1. Non-dynamic members with exact parameter match - eg.
2. Dynamic members
3. Non-dynamic members with casts required - eg.
scope.SetVariable(string, something castable to object)
The result of this being that if you set a variable with a value that
derives from object, you pay for a dynamic lookup (which includes an
exception, which is how I noticed this). Casting the value to object
(ie. scope.SetVariable("value", (object)value)) avoids this, and you
only get the penalty for failing to find the existing variable (unless
it already exists, obviously).