как вернуть null для «метода с плавающей запятой» — обработка ошибок

#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);
    }