- 0 Обсуждение
-
Null considered harmful?
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-объектами.