#c# #exception #exception-handling #error-handling
#c# #исключение #обработка исключений #обработка ошибок
Вопрос:
Я хочу добавить некоторую обработку ошибок в свой код. Я не могу понять, как это сделать для следующего примера:
public class DataPoints
{
public PointF[] RawData {get; set;} //raw measurement pairs
public float xMax; //max value on X axis
public float yMax; //max value on Y axis
public float GetMaxX()
{
if(RawData == null)
{
throw new NullReferenceException();
return null; //THIS does not compile! I want to exit the method here
}
//DO other stuff to find max X
return MAX_X; //as float
}
}
Итак, идея в том, что мне нужно проверить, установлен ли RawData
он уже, а затем выполнить остальные действия в GetMaxX()
методе. Это вообще хорошая практика? Что бы вы сделали в этом случае?
Комментарии:
1. Если вы заинтересованы в возврате null, выберите тип данных с нулевым значением, здесь он ожидает, что вы вернете значение с плавающей запятой (значение null недопустимо)
Ответ №1:
В этом коде есть две проблемы,
Во-первых, вы создаете исключение, за которым следует return — оператор return никогда не будет выполнен, поскольку исключение остановит выполнение остальной части метода, делая оператор return излишним.
Во-вторых, вы не можете вернуть null, если возвращаемый тип равен float; вам нужно будет изменить возвращаемый тип на float ? (см.: типы с нулевым значением)
Так что, если это реальный случай ошибки, поскольку вы ничего не можете сделать, используйте только исключение:
public float GetMaxX()
{
if(RawData == null)
throw new NullReferenceException();
//DO other stuff to find max X
return MAX_X; //as float
}
Или, в качестве альтернативы, верните значение null и удалите исключение:
public float? GetMaxX()
{
if(RawData == null)
return null;
//DO other stuff to find max X
return MAX_X; //as float
}
Лично, если RawData
значение null является условием ошибки / исключительным обстоятельством, которое никогда не должно произойти, тогда я бы сказал, что генерирует исключение и обрабатывает исключение, если оно генерируется в вызывающем коде.
Альтернативным подходом было бы принудительно RawData
инициализировать через конструктор, сделать RawData
private (или, по крайней мере, setter) и создать там исключение. Оставляя любую другую логику в классе чистой от любых исключений, вызывающих / проверяющих нуль, поскольку можно предположить, что RawData
это было установлено ранее.
В результате получается что-то вроде:
public class DataPoints
{
private readonly PointF[] rawData; //raw measurement pairs
public float xMax; //max value on X axis
public float yMax; //max value on Y axis
public DataPoints(PointF[] rawData)
{
if (rawData == null)
throw new ArgumentNullException("rawData");
this.rawData = rawData;
}
public float GetMaxX()
{
//DO other stuff to find max X
return MAX_X; //as float
}
}
Ответ №2:
Если вы создаете исключение, оператор return все равно не будет выполнен, поэтому правильная версия того, что вы пытаетесь, будет
public float GetMaxX()
{
if(RawData == null)
{
throw new NullReferenceException();
}
//DO other stuff to find max X
return MAX_X; //as float
}
Оператор return не будет компилироваться, потому что float — это тип значения, который никогда не может быть null, если вы не используете нулевой тип float?
Лично из приведенного вами примера кода я бы вызвал исключение, поскольку в настоящее время вы предоставляете объект RawData через общедоступный сеттер, поэтому у вас нет гарантии, что он не будет равен null при вызове GetMaxX . Затем исключение может быть распространено вверх по стеку и перехвачено на любом уровне, тогда как, сделав возвращаемый тип nullable, вам придется добавить дополнительные проверки в вызывающий код, чтобы увидеть, вернул ли ваш метод null, и обработать это соответствующим образом.
Ответ №3:
Я не совсем понимаю, что вы хотите сделать в случае ошибки. Вы хотите создать исключение или вернуть null?
Вообще говоря, исключение создается для случаев, когда вызывающий должен был знать лучше, и вы не хотите пытаться восстановить — пусть вызывающий убирает беспорядок.
Возврат null предназначен для случаев, когда у вызывающего абонента могла быть веская причина не инициализировать RawData, что имеет смысл в вашем случае. Для этого вам нужно иметь
public float? GetMaxX()
Если вы действительно хотите взорваться, когда ничего не инициализируется, поместите RawData в качестве параметра в свой конструктор.
Ответ №4:
Вы можете удалить оператор return, который находится ниже оператора исключения throw. Всякий раз, когда генерируется исключение, в методе не выполняется никакой другой оператор (блоки finally являются исключением из этого, но в данном контексте не подлежат возврату).
Кроме этой проблемы с функцией, у меня есть аргумент о том, что свойство RawData доступно публично. Обычно открывать такую коллекцию не очень хорошая идея. Как предполагает @sq33G, вы можете гарантировать наличие допустимого объекта, передав RawData в качестве параметра конструктора. И вы можете потерпеть неудачу на ранней стадии конструктора, когда передается недопустимый массив (null, возможно, нулевого размера?).
private PointF[] _rawData;
public DataPoints(PointF[] rawData)
{
if(rawData == null || rawData.Length == 0)
throw new ArgumentException("RawData should not be null and should contain at least one element");
this._rawData = rawData;
}
Если необходимо, чтобы RawSata был доступен извне класса, я предлагаю вам сделать это таким образом, чтобы ни сам массив (то есть ни установщик), ни его содержимое не могли быть изменены. Использование IEnumerable — правильный способ сделать это.
public IEnumerable<PointF> RawData
{
get { return _rawData; }
}
Ответ №5:
Метод не всегда должен возвращать значение; в частности, ему также разрешено завершать работу, создавая исключение (в этом случае значение не возвращается). Вы можете проверить правило здесь
В вашем примере вы можете либо
(a) вызовите исключение NullReferenceException, которое нарушит поток и вернет
(b) верните значение по умолчанию, если исходные данные равны нулю, что приведет к прерыванию потока и возврату значения по умолчанию.
float f()
{
if (RawData == null)
{
throw new NullReferenceException();
return default(float);
}
return doOtherOperation(RawData);
}
float doOtherOperation(PointF[] RawData)
{
//do what you wanted to do
return default(float);
}