Разница только в объявленном типе переменной. Этот тип будет использоваться компилятором всякий раз, когда вы используете выражение foo
. Например, предположим, что класс Foo
содержит некоторый метод, который не в IFoo
. С первым объявлением этот член не будет виден - вам нужно будет привести foo
к Foo
, чтобы вызвать его. Во втором объявлении у вас будет прямой доступ к нему.
Обратное верно для членов IFoo
, которые реализованы с помощью явной реализации интерфейса в Foo
. Это относительно редко, но это случается.
С первым объявлением вы также можете переназначить переменную в ссылку на любой другой объект типа, реализующего IFoo
, например,
foo = new SomeOtherIFooImplementation();
, тогда как со вторым объявлением вы можете назначать только значения, которые совместимы с Foo
, то есть экземплярами Foo
или производным классом. (В обоих случаях, конечно, вы можете установить переменную в null.)
Часто выгодно кодировать интерфейс, а не конкретную реализацию. Это означает, что компилятор не позволит вам использовать детали, относящиеся к реализации, что, в свою очередь, означает, что в будущем, вероятно, будет проще перейти на другую реализацию.
Тип переменной также влияет на такие вещи, как разрешение перегрузки:
DoSomething(foo);
может вызывать разные методы в зависимости от того, объявлено ли foo
как Foo
или IFoo
.
Как правило, тип переменной во время компиляции важен во всех отношениях - практически каждый раз, когда вы используете переменную, некоторый аспект значения этого кода будет зависеть от типа переменной.