2016-10-24 2 views
1

Рассмотрим интерфейс машинопись с двумя реализациями:Компилятор TypeScript не применяет параметр типа при реализации универсального интерфейса?

interface IFoo {} 

class FooA implements IFoo {} 
class FooB implements IFoo {} 

Далее рассмотрим общий интерфейс, который принимает реализацию IFoo в качестве параметра типа:

interface IFooHandler<F extends IFoo> { 
    handle(foo: F): string 
} 

Теперь давайте реализовывать интерфейс IFooHandler с конкретным применение IFoo в качестве параметра типа:

class FooAHandler implements IFooHandler<FooA> { 
    handle(foo: FooB): string { 
     return "Hello, Foo A!"; 
    } 
} 

Это прекрасно компилируется, используя tsc в версии 2.0.3. Так вот мой вопрос: Почему делает это компилировать?

Обратите внимание, что я использовал FooB в качестве параметра в функции handle(). Я бы предположил, что этот код вызовет ошибку компиляции, поскольку интерфейс IFooHandler<F> предписывает, что метод handle должен принимать параметр типа F (так, в случае IFooHandler<FooA>, типа FooA - и не FooB).

Я не мог найти что-либо по этому поведению в документации машинописи. Является ли это поведение преднамеренным, и если да, то в чем его причина? Или я использую эту функцию неправильно?


Для сравнения, реализуя тот же самый пример в Java (исключая код, так как это очень похоже) дает (ожидаемый) компиляции ошибка:

FooAHandler.java:1: error FooAHandler is not abstract and does not override abstract method handle(FooA) in IFooHandler

+0

Это обсуждается несколько раз в FAQ - https: // github.com/Microsoft/TypeScript/wiki/FAQ#what-is-structural-typing и https: // github.com/Microsoft/TypeScript/wiki/FAQ # when-and-why-are-classes-rated и –

+0

Также в основном дубликат http://stackoverflow.com/questions/32106253/why-does-typescript-implicitly-convert- между-несвязанными классами –

+0

@RyanCavanaugh, спасибо за ссылку. Ой, я не могу поверить, что я пропустил ** буквально первый вопрос ** в FAQ. Во всяком случае, это поведение имеет для меня больше смысла! Спасибо за ваш комментарий. – helmbert

ответ

3

Это происходит потому, что компилятор Безразлично» т сравнить типы по имени, он проверяет их структуру, а с IFoo, FooA и FooB все пусты, то они все равны.

Даже при выполнении:

interface IFoo { 
    a: string; 
} 

class FooA implements IFoo { 
    a: string; 
} 

class FooB implements IFoo { 
    a: string; 
} 

Компилятор все равно не будет жаловаться, но как только мы различаем между FooA и FooB:

class FooA implements IFoo { 
    a: string; 
    b: string; 
} 

class FooB implements IFoo { 
    a: string; 
    c: string; 
} 

Мы получаем ошибку компилятора:

Class 'FooAHandler' incorrectly implements interface 'IFooHandler<FooA>'. 
    Types of property 'handle' are incompatible. 
    Type '(foo: FooB) => string' is not assignable to type '(foo: FooA) => string'. 
     Types of parameters 'foo' and 'foo' are incompatible. 
     Type 'FooA' is not assignable to type 'FooB'. 
      Property 'c' is missing in type 'FooA'. 

(code in playground)

Примечание стороны:
Пустые объекты (например, все ваши интерфейсы) будет всегда принимает все:

interface IFoo { } 

function fn(foo: IFoo) {} 

fn(3); // ok 
fn("string"); // ok 
fn({ key: "value" }); // ok 

(code in playground)

+0

+1, отличный ответ, спасибо! Возможно, я действительно упростил мой пример, так как мои фактические интерфейсы, конечно, не все пустые, но этот ответ заставил меня на правильном пути. – helmbert

+0

Есть ли способ получить изначально ожидаемое поведение? – Kir

+0

@ Что такое «изначально ожидаемое поведение»? Если вы имеете в виду, что вы хотите, чтобы компилятор сравнивал типы по их объявленным типам, отличным от их структуры, то нет, это именно то, как работает компилятор ts. –

Смежные вопросы