ФЭНДОМ


Null considered harmful?Править

К написанию этой статьи подтолкнуло разъяснение Дэвида Симмонса о различиях в подходах обработки значений null/nil между C# и S#.

Несмотря на то, что популярные коммерческие языки заявляют о все большей безопасности системы типов, в действительности дела обстоят не так уж радужно. Основных проблем, в принципе, всего две - необходимость приведения типов и наличие null.

Остановимся подробнее на последней.

Как известно, null - это нетипизированное "ничто". Это ссылка на несуществующий объект, которую можно подставить в качестве объекта любого типа. Если по такой ссылке попытаться обратиться или вызвать метод, то возникнет исключение NullReferenceException. Эта концепция берет свое начало от C-ишного (void*)0 или NULL с улучшенной обработкой ошибочных ситуаций.

А причина наличия именно такого null возникает из-за манифестной типизации (manifest typing) и необходимости обозначения отсутствия осмысленного значения в переменной.

В итоге самая правильная на вид программа может выдавать ошибку все по тому же исключению из-за того, что явно или неявно передали null (например, как результат преобразования типов).

 public static string func( Object obj )
 {
   return obj.ToString();
 }
 func(null);

К тому же, то, что null может скрываться под любым типом создает неудобный дуализм - может это реальный объект, а может это null. Тогда для обработки таких ситуаций нужны постоянные проверки

 if (obj != null) { ... } else { ... }

Частично проблема решается введением Null Object Pattern, но это возможно далеко не всегда, да и применяется не так часто.

Если отвлечься от текущего наследия .NET/CLR, то с null, и с семантикой в общем, можно было бы сделать следующее.

  • Сделать null типизированным, т.е. пусть он будет синглтоном класса UndefinedObject.
  • Каждый тип сделать вариантным: SomeType = SomeType! | null, где null обозначается равенство собственно объекту null, SomeType! - это строго экземпляр класса SomeType. В принципе, можно пойти от обратного, и по умолчанию сделать типизацию более строгой, т.е. SomeType? = SomeType | null.
  • В классе UndefinedObject ввести метод
 public virtual Object doesNotUnderstand( string name, object[] params )
 {
   throw new NullReferenceException(name, params);
   return this;
 }
  • При вызове метода через ссылку SomeType! вызывать нужный метод.
  • При вызове метода через ссылку SomeType и если при этом объектом-получателем оказался null, то вызов переадресовать UndefinedObject.doesNotUnderstand, что вызовет нужное исключение.

То есть до этого момента мы получили то же самое, только с улучшенной типизацией за счет наличия SomeType! и типизированного null. Соответственно

 SomeType! var;                  // ошибка
 SomeType! var = null;           // ошибка
 SomeType! var = new SomeType(); // правильно

Далее можно проделать следующее.

У нас имеется класс UndefinedObject. Ни в коем случае не будем делать его sealed (sealed considered harmful? ), а позволим создавать подклассы и переопределять метод doesNotUnderstand.

Создание таким объектов будет осуществляться точно так же с помощью оператора new. При это объект null будет заранее созданным экземпляром UndefinedObject.

Стоит отметить, что реализация таких возможностей не требует жертвовать производительностью обычных операций. Ссылки на null-объекты можно регистрировать в пределах защищенной памяти и с помощью аппаратного исключения диспетчеризовать вызов к doesNotUnderstand.

В качестве примера использования новых возможностей можно реализовать так называемый "глухой объект":

 class DeafObject : UndefinedObject
 {
   public override Object doesNotUnderstand( string name, object[] params )
   {
     return this;
   }
 }

Таким образом, в ответ на вызов любого метода, будет выполняться обработчик doesNotUnderstand, возвращающий самого объекта. Эти вызовы будут все время "проглатываться". Этот объект позволяет легко реализовать Null Object Pattern для любого типа. В отличие от стандартного C#/Java, где для каждого типа объекта придется создавать персональный null-объект.

Среди прочих возможностей - мониторинг обращений к заданному объекту и различные схемы с proxy-объектами.

Владимир Лешкевич