What might be tripping you up is that it's a little confusing that
interfaces are also types, even though they don't describe an entire
type as such. They are more of a partial type, or even more accurately,
a template specification for a partial type. Or more accurately yet, a
description of part of the interface of a type.
What you're actually getting back from that call to ExecuteReader() is
some instance of a class that, amongst other things, implements the
IDataReader interface. But you're viewing that instance through the
"lens" of the interface. Using the interface "type" lets you work with
any object that implements the interface, but you can only "see" and
work with the portion of the object that implements the interface. Even
though the complete instance with all its data and behaviors is in
memory, you can't get to all of it.
Really, when you think about it, interfaces are a way to partially get
around the restrictions of strong typing when the exact full type of an
object isn't known at design time. Unlike .NET, loosely typed,
late-bound environments have less need to support interfaces because the
runtime cares more about what the object *does* than what it *is*. So
if you call Foo() on some object in a loosely typed, late-bound
environment, all that matters is that the object has a Foo() method, and
you'll only get an error if the object lacks the method. Whereas in
..NET, everything is wired up at compile time. Interfaces are a way to
assure the compiler that while the object's type can't be known to the
compiler, it is at least known to implement Foo().
--Bob