Безопасность типов означает, что компилятор будет проверять типы во время компиляции и выдавать ошибку, если вы попытаетесь присвоить переменной неверный тип.
Несколько простых примеров:
// Fails, Trying to put an integer in a string
String one = 1;
// Also fails.
int foo = "bar";
Это также относится к аргументам метода, поскольку вы передаете им явные типы:
int AddTwoNumbers(int a, int b)
{
return a + b;
}
Если я попытался вызвать это, используя:
int Sum = AddTwoNumbers(5, "5");
Компилятор выдаст ошибку, потому что я передаю строку ("5"), и она ожидает целое число.
На свободно набранном языке, например на javascript, я могу сделать следующее:
function AddTwoNumbers(a, b)
{
return a + b;
}
если я назову это так:
Sum = AddTwoNumbers(5, "5");
Javascript автоматически преобразует 5 в строку и возвращает «55». Это связано с тем, что javascript использует знак + для конкатенации строк. Чтобы сделать это с учетом типов, вам нужно сделать что-то вроде:
function AddTwoNumbers(a, b)
{
return Number(a) + Number(b);
}
Или, возможно:
function AddOnlyTwoNumbers(a, b)
{
if (isNaN(a) || isNaN(b))
return false;
return Number(a) + Number(b);
}
если я назову это так:
Sum = AddTwoNumbers(5, " dogs");
Javascript автоматически преобразует 5 в строку и добавляет их, чтобы вернуть «5 собак».
Не все динамические языки так же прощаемы, как javascript (фактически динамический язык не подразумевает свободный типизированный язык (см. Python)), некоторые из них на самом деле приведут к ошибке времени выполнения при неверном приведении типов.
Несмотря на удобство, он открывает множество ошибок, которые можно легко пропустить, и которые можно определить только путем тестирования работающей программы. Лично я предпочитаю, чтобы мой компилятор сообщал мне, допустил ли я эту ошибку.
Теперь вернемся к C # ...
C # поддерживает языковую функцию, называемую covariance , это в основном означает, что вы можете заменить базовый тип дочерним типом и не вызвать ошибку, например:
public class Foo : Bar
{
}
Здесь я создал новый класс (Foo), который является подклассом Bar. Теперь я могу создать метод:
void DoSomething(Bar myBar)
И вызовите его, используя Foo или Bar в качестве аргумента, оба будут работать без ошибок. Это работает, потому что C # знает, что любой дочерний класс Bar будет реализовывать интерфейс Bar.
Однако вы не можете сделать обратное:
void DoSomething(Foo myFoo)
В этой ситуации я не могу передать Bar этому методу, потому что компилятор не знает, что Bar реализует интерфейс Foo. Это потому, что дочерний класс может (и обычно будет) сильно отличаться от родительского класса.
Конечно, теперь я далеко ушёл от глубокого конца и выходит за рамки оригинального вопроса, но это всё хорошо знать:)