// Этот код компилируется, но он принципиально неверный!
iOb = strOb;
// Следующая строка кода приводит к исключительной
// ситуации во время выполнения.
// v = (int) iOb.GetObO; // Ошибка при выполнении!
}
}
При выполнении этой программы получается следующий результат.
Тип переменной ob: System.Int32
Значение: 102
Тип переменной ob: System.String
Значение: Тест на необобщенность
Как видите, результат выполнения этой программы такой же, как и у предыдущей программы.
В этой программе обращает на себя внимание ряд любопытных моментов. Прежде всего, тип Т заменен везде, где он встречается в классе NonGen
. Благодаря этому в классе NonGen
может храниться объект любого типа, как и в обобщенном варианте этого класса. Но такой подход оказывается непригодным по двум причинам. Во-первых, для извлечения хранящихся данных требуется явное приведение типов. И во-вторых, многие ошибки несоответствия типов не могут быть обнаружены вплоть до момента выполнения программы. Рассмотрим каждую из этих причин более подробно.
Начнем со следующей строки кода.
int v = (int) iOb.GetOb();
Теперь возвращаемым типом метода GetOb()
является object
, а следовательно, для распаковки значения, возвращаемого методом GetOb()
, и его последующего сохранения в переменной v требуется явное приведение к типу int
. Если исключить приведение типов, программа не будет скомпилирована. В обобщенной версии этой программы приведение типов не требовалось, поскольку тип int
указывался в качестве аргумента типа при создании объекта iOb
. А в необобщенной версии этой программы потребовалось явное приведение типов. Но это не только неудобно, но и чревато ошибками.
А теперь рассмотрим следующую последовательность кода в конце анализируемой здесь программы.
// Этот код компилируется, но он принципиально неверный!
iOb = strOb;
// Следующая строка кода приводит к исключительной
// ситуации во время выполнения.
// v = (int) iOb.GetObO; // Ошибка при выполнении!
В этом коде значение переменной strOb
присваивается переменной iOb
. Но переменная strOb
ссылается на объект, содержащий символьную строку, а не целое значение. Такое присваивание оказывается верным с точки зрения синтаксиса, поскольку все ссылки на объекты класса NonGen
одинаковы, а значит, по ссылке на один объект класса NonGen
можно обращаться к любому другому объекту класса NonGen
. Тем не менее такое присваивание неверно с точки зрения семантики, как показывает следующая далее закомментированная строка кода. В этой строке тип, возвращаемый методом GetOb()
, приводится к типу int
, а затем предпринимается попытка присвоить полученное в итоге значение переменной int
. К сожалению, в отсутствие обобщений компилятор не сможет выявить подобную ошибку. Вместо этого возникнет исключительная ситуация во время выполнения, когда будет предпринята попытка приведения к типу int. Для того чтобы убедиться в этом, удалите символы комментария в начале данной строки кода, скомпилируйте, а затем выполните программу. При ее выполнении возникнет ошибка.
Упомянутая выше ситуация не могла бы возникнуть, если бы в программе использовались обобщения. Компилятор выявил бы ошибку в приведенной выше последовательности кода, если бы она была включена в обобщенную версию программы, и сообщил бы об этой ошибке, предотвратив тем самым серьезный сбой, приводящий к исключительной ситуации при выполнении программы. Возможность создавать типизированный код, в котором ошибки несоответствия типов выявляются во время компиляции, является главным преимуществом обобщений. Несмотря на то что в C# всегда имелась возможность создавать "обобщенный" код, используя ссылки на объекты, такой код не был типизированным, т.е. не обеспечивал типовую безопасность, а его неправильное применение могло привести к исключительным ситуациям во время выполнения. Подобные ситуации исключаются благодаря обобщениям. По существу, обобщения переводят ошибки при выполнении в разряд ошибок при компиляции. В этом и заключается основная польза от обобщений.
В рассматриваемой здесь необобщенной версии программы имеется еще один любопытный момент. Обратите внимание на то, как тип переменной ob экземпляра класса NonGen
создается с помощью метода ShowType()
в следующей строке кода.
Console.WriteLine("Тип переменной ob: " + ob.GetType());