Что такое неявно типизированная переменная
Неявно типизированные поля в C#
Сегодня на кывте был задан очередной весьма интересный вопрос о том, почему в языке C# существуют неявно типизированные локальные переменные (implicitely-typed local variables) a.k.a. var, но нет неявно типизированных полей?
На самом деле, такое положение дел вовсе не случайно; так что давайте рассмотрим несколько причин, почему компилятор ведет себя именно так, а не иначе.
Во-первых, возможность использовать var, для объявления неявно типизированных локальных переменных, никогда не была самостоятельной возможностью. При разработке языка C# никто не ставил перед собой цели создать полностью неявно типизированный язык программирования, типа F#; неявная типизация была лишь одной составляющей (пусть и немаловажной) более общей концепции, известной сегодня под аббревиатурой LINQ.
Поскольку при разрабоке LINQ-а было принято решение, что завязывать разработчика только на существующие типы является слишком «злым» ограничением, то при ее реализации была предоставлена разработчику возможность возвращать последовательности анонимных классов. А раз так, то без использования неявно типизированных переменных это было бы не возможно:
Однако использование ключевого слова var в объявлении типа никак бы не помогло решить более глобальную цель (это я опять про LINQ).
Во-вторых, даже если не залазить слишком глубоко в то, насколько сложно было бы реализовать такую возможность компиляторописателям (*), даже навскидку с ее реализацией и использованием связаны некоторые недостатки. Первый недостаток связан с тем, что неявно типизированные поля будут плохо дружить с анонимными типами.
Давайте представим себе следующий класс:
Еще одним важным ограничением является то, что неявно типизированные поля вводят слишком тесную связь между инициализируемым полем и «инициализатором». Давайте рассмотрим такой пример: предположим, у нас есть класс A, который содержит “var”-поле с именем foo, которое инициализируется путем вызова статического метода Foo класса B:
Это приводит к целому ряду дополнительных вопросов: а что будет, если классы A и B расположены в разных сборках? А что если без перекомпиляции сборки A будет перекомпилирована сборка с классом B и тип возвращаемого значения метода Foo изменится с типа int на string? Или же у нас может быть еще неявно типизированное поле C.foo (т.е. поле foo в классе С), завязанное на тип поля A.foo, поле D.foo, завязанное на поле C.foo и т.д. и тогда изменение типа возвращаемого значения одной функции приведет к изменению типов полей в десятке разных классов (**). Да и вообще, поля являются более важной частью дизайна класса и его реализации по сравнению с локальными переменными, поэтому изменение «на лету» типа этого поля, только из-за того кто-то поменял сигнатуру функции в третьем модуле является не лучшей идеей.
Конечно же, можно было бы ограничить возможность использования неявно типизированных полей лишь в ограниченном наборе случаев, и запрещать использование этой возможности с анонимными типами и классами из других сборок, а оставить, например, только использование методов текущего класса. Но даже в этом случае реализация этой возможности требует слишком больших усилий на реализацию (пруф у Эрика, я тут не причемJ), что делает ее довольно низкоприоритетной в бесконечном списке улучшений, которые есть в головах разработчиков компилятора языка C#.
(*) Залазить «в дебри» нет никакого смысла, поскольку это уже сделал Эрик Липперт в своей заметке Why no var on fields?, в которой он как раз и рассказывает о том, что реализация неявно типизированных полей потребовала бы значительно более существенных затрат на реализацию, нежели реализация неявно типизированных локальных переменных.
(**) Конечно, это не показатель, и любую возможность использовать правильно или не правильно. Но с помощью этой штуки можно нагородить такого, что пример, приведенный в заметке Как не надо писать код случай, может еще и цветочками показаться (поскольку мелкие изменения будут приводить к мессу не в одном классе, а еще и в десятке других).
Идентификаторы и переменные
Переменные и типы данных с примерами на C#
Идентификаторы и переменные
Идентификатор — это имя, которое вы присваиваете типу, члену, переменной или пространству имен. Допустимые идентификаторы должны:
Переменная — это идентификатор, указывающий на место хранения в памяти, которое содержит его значение. Название переменной не должно быть из числа ключевых слов (предварительно определенные зарезервированные идентификаторы, которые имеют специальные значения для компилятора) или иметь буквальный идентификатор (префикс @ ).
Объявление переменных и типы
Все переменные в коде имеют тип, — многообразие концепций, определяющих возможные значения переменных, их смысл, операции, а также способы хранения этих значений.
Все переменные в C# должны быть объявлены до их применения. Тип переменной нельзя изменять в течение срока ее существования. Объявление переменных в коде выполняется с оператором объявления, который в C # выглядит следующим образом:
Пример объявления переменной i типа integer: int i;
После определения переменной можно присвоить некоторое значение, например: i = 256;
Прием, когда при определении переменной ей сразу присваивается значение называется инициализацией: int i = 256;
В C# существуют две разновидности типов: ссылочных и значений. С общими сведениями о системе типов в C# можно ознакомиться на странице официальной документации.
Переменные типа значения (Value types) сохраняют свое значение в своем собственном распределении памяти. Каждая переменная типа значения имеет собственную копию данных, и операции над одной переменной не могут затрагивать другую (за исключением переменных параметров ref и out ). Тип значения должен иметь значение и не может быть установлен равным нулю, не представляя значения. По этой причине, когда вы объявляете переменную типа значения без инициализатора, она неявно инициализируется значением по умолчанию данного типа.
Так, при вызове методов, происходит «передача по значению», вы, по сути, клонируете значение, поэтому любые изменения, которые вы вносите в свою переменную в своем методе, не будут отражены в вашей исходной переменной.
static void Main(string[] args)
<
string myValue = “Data before change”;
myMethod(myValue);
Console.Write(myValue); // Значение прежнее — “Data before change”.
>
public static void myMethod(string myValue)
<
myValue = “Data after change”;
>
Типы значений, допускающие значение NULL
Переменные ссылочных типов (Reference types) хранят ссылки на нужные данные, которые именуются объектами. Две переменные ссылочного типа могут ссылаться на один и тот же объект, поэтому может случиться так, что операции над одной переменной затронут объект, на который ссылается другая переменная.
Когда вы передаете ссылочный тип методу, вы передаете ссылку на значение, а не само значение, поэтому изменения, которые вы применяете в методах по отношению к переменной ссылочного типа, будут влиять и на исходную переменную.
Для объявления ссылочных типов используются следующие ключевые слова: class, interface, delegate. В C# также предусмотрены следующие встроенные ссылочные типы: dynamic, object, string.
static void Main(string[] args)
<
Person person = new Person();
person.Name = “Bill”; // Имя до изменения — “Bill”.
myMethod(person);
Console.Write(person.Name); // На консоль будет выведен — “Petr”
>
public static void myMethod(Person person)
<
person.Name = “Petr”;
>
Константы
Другой тип объявления, который применяется в коде, называется константой (const). Как только постоянное значение установлено, оно не может быть изменено.
Примитивные и встроенные типы
При объявлении переменной можно использовать как алиас встроенного типа, так и тип источника, которому соответствует примитивный тип данных C#. Встроенные типы явно определены в спецификации языка.
Примитивный тип может быть использован в виде литерального значения — элемента программы, который непосредственно представляет значение. Особым литералом является ключевое слово null.
Строковый типы данных (string) хотя и не является примитивным типом, его можно использовать в качестве значения для константы или литерального значения в коде.
Строковый тип
Строковый тип (string) — это класс, последовательная коллекция System.Char объектов, представляющих строку. Объект System.Char представляет символ как кодовую единицу таблицы Юникод UTF-16.
Неявно типизированные локальные переменные
Локальные переменные можно объявлять без указания конкретного типа. Неявная типизация — типизация, при которой переменная остается строго типизированной, а её тип определяется компилятором, исходя из присваиваемого значения.
IT1300: Императивное программирование
Неявно типизированная переменная объявляется с помощью ключевого слова var и должна быть непременно инициализирована. Для определения типа этой переменной компилятору служит тип ее инициализатора, т.е. значения, которым она инициализируется. Рассмотрим такой пример.
В приведенном ниже примере программы демонстрируется применение неявно типизированных переменных. Он представляет собой вариант программы из предыдущего раздела, измененной таким образом, чтобы все переменные были типизированы неявно.
Результат выполнения этой программы оказывается таким же, как и прежде.
Важно подчеркнуть, что неявно типизированная переменная по-прежнему остается строго типизированной. Обратите внимание на следующую закомментированную строку из приведенной выше программы.
Неявно типизированные переменные внедрены в C# не для того, чтобы заменить собой обычные объявления переменных. Напротив, неявно типизированные переменные предназначены для особых случаев, и самый примечательный из них имеет отношение к языку интегрированных запросов (LINQ), подробно рассматриваемому в главе LINQ. Таким образом, большинство объявлений переменных должно и впредь оставаться явно типизированными, поскольку они облегчают чтение и понимание исходного текста программы.
И последнее замечание: одновременно можно объявить только одну неявно типизированную переменную. Поэтому объявление
var s1 = 4.0, s2 = 5.0; // Ошибка!
Что такое неявно типизированная переменная
Спецификации C# 1.2 и 2.0 гораздо объемнее (более 100 стр.) по сравнению с C# 3.0, так как содержат описания основ языка, и доступны по ссылке http://msdn.microsoft.com/vcsharp/language.
Неявно типизированные локальные переменные
При объявлении неявно типизированной локальной переменной ее тип определяется из инициализирующего выражения. Например:
Тип var не был определен в коде, при этом объявления эквивалентны следующему:
Для неявно типизированных переменных существуют следующие ограничения:
Если объявление локальной переменной содержит несколько вложенных объявлений (например, при создании массива), все инициализаторы должны иметь один тип на момент компиляции.
Примеры нарушения ограничений:
Неявно типизированные массивы
В последнем случае происходит ошибка компиляции, так как невозможно неявно преобразовать int в string и наоборот.
Зачем нужна неявная типизация?
Неявно типизированные переменные и массивы могут быть полезны начинающим программистам, так как еще сильнее снижают требования к начальному уровню подготовки. Можно просто писать код и создавать там переменные, не зная о типах практически ничего.
Кроме того, возможности неявной типизации могут быть полезны программистам, работающим на языках типа php, где возможности неявного определения типа были заложены изначально. Они могут получить преимущества управляемого программирования, перейдя на C# и сохраняя при этом привычный стиль программирования.
Однако возможности неявной типизации, без сомнения, могут служить источником дополнительных ошибок и делают код менее понятным.
Расширяющие методы
Расширяющие методы, очевидно, полезны для добавления функциональности в сторонние классы. Однако они могут служить источником путаницы и дополнительных ошибок. Кроме того, расширяющие методы труднее обнаружить (в описании расширяемого класса их нет). Расширения свойств, событий или операторов пока не поддерживаются. Возможно, они появятся в C# позднее.
Лямбда-выражения
C# 2.0 поддерживал анонимные методы, позволяющие писать блоки кода вместо параметров-делегатов. Анонимные методы предоставляют значительную часть возможностей для функционального программирования, но при этом обладают достаточно сложным синтаксисом. Лямбда-выражения предоставляют более краткий функциональный синтаксис написания анонимных методов.
Переменные
Синтаксис объявления переменных в C# выглядит следующим образом:
Объявить можно переменную любого действительного типа. Важно подчеркнуть, что возможности переменной определяются ее типом. Например, переменную типа bool нельзя использовать для хранения числовых значений с плавающей точкой. Кроме того, тип переменной нельзя изменять в течение срока ее существования. В частности, переменную типа int нельзя преобразовать в переменную типа char.
Все переменные в C# должны быть объявлены до их применения. Это нужно для того, чтобы уведомить компилятор о типе данных, хранящихся в переменной, прежде чем он попытается правильно скомпилировать любой оператор, в котором используется переменная. Это позволяет также осуществлять строгий контроль типов в C#.
Инициализация переменной
Задать значение переменной можно, в частности, с помощью оператора присваивания. Кроме того, задать начальное значение переменной можно при ее объявлении. Для этого после имени переменной указывается знак равенства (=) и присваиваемое значение. Если две или более переменные одного и того же типа объявляются списком, разделяемым запятыми, то этим переменным можно задать, например, начальное значение. Ниже приведена общая форма инициализации переменной:
Инициализация переменных демонстрирует пример обеспечения безопасности C#. Коротко говоря, компилятор C# требует, чтобы любая переменная была инициализирована некоторым начальным значением, прежде чем можно было обратиться к ней в какой-то операции. В большинстве современных компиляторов нарушение этого правила определяется и выдается соответствующее предупреждение, но «всевидящий» компилятор C# трактует такие нарушения как ошибки. Это предохраняет от нечаянного получения значений «мусора» из памяти, оставшегося там от других программ.
В C# используются два метода для обеспечения инициализации переменных перед пользованием:
Переменные, являющиеся полями класса или структуры, если не инициализированы явно, по умолчанию обнуляются в момент создания.
Переменные, локальные по отношению к методу, должны быть явно инициализированы в коде до появления любого оператора, в котором используются их значения. В данном случае при объявлении переменной ее инициализация не происходит автоматически, но компилятор проверит все возможные пути потока управления в методе и сообщит об ошибке, если обнаружит любую возможность использования значения этой локальной переменной до ее инициализации.
Например, в C# поступить следующим образом нельзя:
Динамическая инициализация
В приведенных выше примерах в качестве инициализаторов переменных использовались только константы, но в C# допускается также динамическая инициализация переменных с помощью любого выражения, действительного на момент объявления переменной:
В данном примере объявляются три локальные переменные i1, i2, result, первые две из которых инициализируются константами, а переменная result инициализируется динамически с использованием метода Math.Sqrt(), возвращающего квадратный корень выражения. Следует особо подчеркнуть, что в выражении для инициализации можно использовать любой элемент, действительный на момент самой инициализации переменной, в том числе вызовы методов, другие переменные или литералы.
Неявно типизированные переменные
Неявно типизированная переменная объявляется с помощью ключевого слова var и должна быть непременно инициализирована. Для определения типа этой переменной компилятору служит тип ее инициализатора, т.е. значения, которым она инициализируется:
Единственное отличие неявно типизированной переменной от обычной, явно типизированной переменной, — в способе определения ее типа. Как только этот тип будет определен, он закрепляется за переменной до конца ее существования.
Неявно типизированные переменные внедрены в C# не для того, чтобы заменить собой обычные объявления переменных. Напротив, неявно типизированные переменные предназначены для особых случаев, и самый примечательный из них имеет отношение к языку интегрированных запросов (LINQ). Таким образом, большинство объявлений переменных должно и впредь оставаться явно типизированными, поскольку они облегчают чтение и понимание исходного текста программы.
Давайте рассмотрим пример, где в консоль будем выводить типы неявно типизированных переменных: