التباين يصف كيفية ارتباط النوع الفرعي للنوع المركب بالنوع الفرعي لأجزائه — أي متى يكون Container<Sub> قابلاً للإسناد إلى Container<Super>؟
ليكن Dog نوعاً فرعياً من Animal.
let dogs: Dog[] = [];
let animals: Animal[] = dogs; // ✅ Dog[] is assignable to Animal[]
أنواع الإرجاع والمصفوفات متباينة معاً: إذا كان Dog ⊆ Animal، فإن Dog[] ⊆ Animal[]. دالة ترجع Dog قابلة للاستخدام حيث يتوقع واحدة ترجع Animal.
type Handler<T> = (arg: T) => void;
let animalHandler: Handler<Animal> = (a) => {};
let dogHandler: Handler<Dog> = animalHandler; // ✅ (with strictFunctionTypes)
// a handler that accepts ANY Animal can safely handle a Dog
معاملات الدوال متناقضة التباين: Handler<Animal> قابلة للإسناد إلى Handler<Dog>، وهو العكس من علاقة العنصر. هذا صحيح منطقياً — شيء يتعامل مع جميع الحيوانات بالتأكيد يتعامل مع الكلاب.
// Method parameters in TS are bivariant by default (a known unsound convenience)
interface Comparer<T> { compare(a: T): void; }
TypeScript يفحص أنواع الدوال المستقلة بشكل متناقض التباين فقط تحت strictFunctionTypes؛ معاملات الطريقة هي ثنائية التباين عن قصد لأسباب الراحة، وهذا غير آمن من الناحية التقنية.
التباين يشرح لماذا يُسمح بتعيينات معينة أو يتم رفضها — لماذا Dog[] تناسب Animal[] لكن callback (d: Dog) => void لا يمكنه دائماً أن يحل محل (a: Animal) => void.