#c# #coding-style #exception-safety
#c# #стиль кодирования #исключение-безопасность
Вопрос:
Как я могу убедиться, что определенный экземпляр класса никогда не будет равен нулю? Кто-то сказал мне использовать Debug.Assert() но, поступая таким образом, я бы только гарантировал, что код работает в режиме отладки, в то время как я хочу обеспечить условие is-never-null также в release.
Например, в прошлом я писал код, подобный:
public string MyString
{
get
{
if(instance1.property1.Equals("bla"))
{
return bla;
}
}
}
Но это приводит к возникновению исключения, если значение instance1 равно нулю. Я хотел бы избежать подобных ошибок и генерации подобных исключений в будущем.
Спасибо,
пожалуйста, посмотрите конкретный пример ниже, который иллюстрирует проблему:
У меня есть метод, который проверяет подлинность пользователей на основе ответов с сервера. Метод заключается в следующем:
/// <summary>
/// attempts authentication for current user
/// </summary>
/// <returns></returns>
public AuthResult CheckUser()
{
WebRequest request = WebRequest.Create(GetServerURI);
request.Method = "POST";
request.ContentType = "application/x-www-form-urlencoded";
string postdata = "data=" HttpUtility.UrlEncode(SerializeAuth());
byte[] arr = Utils.AppDefaultEncoding.GetBytes(postdata);
request.ContentLength = arr.Length;
request.Timeout = Convert.ToInt32(TimeUtils.GetMiliseconds(10, TimeUtils.TimeSelect.Seconds));
Stream strToWrite = request.GetRequestStream();
strToWrite.Write(arr, 0, arr.Length);
WebResponse response = request.GetResponse();
using (Stream dataFromResponse = response.GetResponseStream())
{
using (StreamReader reader = new StreamReader(dataFromResponse))
{
string readObj = reader.ReadToEnd();
return DeserializeAuth(readObj);
}
}
}
для вызова этого метода я использую
_authenticationResult = authObj.CheckUser();
У меня также есть это свойство, среди прочих
public ResultType AuthResult
{
get
{
if (_authenticationResult.auth == "1")
return ResultType.Success;
if (_authenticationResult.auth == "0")
return ResultType.FailAccountExpired;
if (_authenticationResult.auth == "-1")
return ResultType.FailWrongUsernameOrPassword;
if (_authenticationResult.auth == "-2")
return ResultType.Banned;
return ResultType.NoAuthDone;
}
}
public enum ResultType { Success, FailWrongUsernameOrPassword, FailAccountExpired, NoAuthDone, Banned }
что произошло, так это то, что _authenticationResult был равен нулю один раз, и свойство AuthResult выдало nullref при попытке «null.auth». Как я могу гарантировать (возможно, внутри метода CheckUser ()), что он никогда не возвращает null.
Когда я отлаживал приложение, этого никогда не происходило. Но в рабочей среде, когда время ожидания сервера истекало, иногда метод возвращал значение null.
Спасибо,
Комментарии:
1. Этот код на самом деле не будет работать — вам нужно всегда возвращать что-то из
get
AFAIK… что должно произойти в режиме выпуска, когдаinstance1
этоnull
?2. спасибо — это всего лишь некоторый псевдокод. Обычно я также записываю эти свойства со вторым возвратом после блока if. В режиме выпуска, если instance1 равен нулю, я получаю исключение nullref. Я считаю, что то, что я делаю, — плохая практика кодирования, и, возможно, можно сделать что-то более элегантное.
3. @unholysampler да… мой вопрос был не в том, что происходит, когда, например,
instance1
естьnull
, а в том, что должно произойти в таком случае? конкретное исключение?4. В чем разница между debug и release? Обычно
assert
включен во время отладки и выключен в релизной версии.5. Глядя на опубликованный вами пример кода, я думаю, что элегантный способ сделать это — реорганизовать ваш класс (возможно, разбив его на два класса или другие подходы, которые мы могли бы обсудить), чтобы у вашего вызывающего абонента не было абсолютно никакого способа вызвать AuthResult до строки _authenticationResult = authObj . CheckUser(); выполняется
Ответ №1:
Я думаю, вам нужно понять, как instance1
и впоследствии property1
будут созданы экземпляры, и создавать их только таким образом, чтобы они не могли быть нулевыми. Обычно это делается путем проверки аргументов при построении, например:
public instance1(string property1)
{
if (property1 == null) throw new ArgumentNullException("property1");
this.property1 = property1;
}
Если вы создаете свои типы таким образом, чтобы они не могли существовать в недопустимом состоянии, вы гарантируете, что ваш зависимый код не будет зависать от null
значений.
В противном случае нам нужно было бы увидеть более полный пример того, что вы делаете, чтобы дать вам более конкретный совет.
Еще одна вещь, которую следует учитывать, это то, в каком состоянии может существовать ваш класс, в котором это обязательное состояние работы или необязательное состояние работы. Таким образом, какие члены требуются для работы вашего класса, и вы должны стремиться проектировать свои классы таким образом, чтобы они всегда имели требуемое состояние, например:
public class Person
{
public Person(string forename, string surname)
{
if (forename == null) throw new ArgumentNullException("forename");
if (surname == null) throw new ArgumentNullException("surname");
Forename = forename;
Surname = surname;
}
public string Forename { get; private set; }
public string Surname { get; private set; }
}
В моем примере типа я требую, чтобы мои Forename
и Surname
значения имели ненулевое значение. Это обеспечивается с помощью моего конструктора… мой Person
тип никогда не может быть создан с нулевыми значениями (хотя, возможно, пустые значения так же плохи, поэтому проверка IsNullOrWhiteSpace
и выбрасывание соответствующего ArgumentException
— это маршрут, но давайте сохраним его простым).
Если бы я должен был ввести необязательное поле, я бы позволил ему изменять состояние моего Person
экземпляра, например, присвоить ему параметр setter:
public class Person
{
public Person(string forename, string surname)
{
if (forename == null) throw new ArgumentNullException("forename");
if (surname == null) throw new ArgumentNullException("surname");
Forename = forename;
Surname = surname;
}
public string Forename { get; private set; }
public string Surname { get; private set; }
public string Initial { get; set; }
}
Мой Person
тип по-прежнему применяет обязательные поля для работы, но вводит необязательное поле. Затем мне нужно принять это во внимание при выполнении операции, в которой используются эти члены:
public override ToString()
{
return Forename (Initial == null ? String.Empty : " " Initial) " " Surname;
}
(Хотя это не лучший пример ToString
).
Комментарии:
1. это очень хороший совет, я запомню это на будущее. Я также обновил начальный пост конкретным примером в соответствии с вашим запросом. Спасибо
2. Я чувствую, что проверять, является ли строка нулевой, довольно глупо. Если вы инициализируете все строки как пустую строку, вам не нужно беспокоиться о нулевом значении, и всегда есть IsNullOrEmpty .
3. @Ramhound Иногда это может иметь место, но может возникнуть ситуация, когда должна быть разница между тем, что что-то является
null
по сравнениюEmpty
с тем, что имеет значение. Это будет зависеть от того, как он ожидает, что потребители его типов будут вести себя с этими разными значениями.
Ответ №2:
Вы можете использовать:
if ( instance1 != null amp;amp; instance1.property1.Equals("bla")){
// Your code
}
Комментарии:
1. Спасибо за ваш ответ, я знаю это, я делаю это постоянно. Я искал что-то вроде хорошей практики кодирования, какой-нибудь ответ в духе модульных тестов или что-нибудь подобное.
2. Ваше решение полезно, но все еще не то, что я искал. Но я был неоднозначен в своем первоначальном сообщении — проверка того, равен ли объект нулю, обрабатывает следствие, а не причину — мне нужно обработать саму причину, чтобы никогда не допускать, чтобы объект был нулевым в первую очередь.
3. Тогда вы несете ответственность за правильную инициализацию instance1.
4. @Andrey Cristof — Проверка того, равен ли объект нулю, прежде чем пытаться использовать ссылку на него, не является «обработкой эффекта», как вы выразились. Вы даже осознаете, что ваши действия не являются «правильным» способом сделать это.
Ответ №3:
Лично я бы использовал ?? оператор (предполагается property1
, что это строка)
public string MyString
{
get { instance1.property1 ?? "Default value"; }
}
Ответ №4:
Люди обычно справляются с этой ситуацией одним из трех способов. Наихудший способ (на мой взгляд) — быть параноиком в отношении каждой ссылки, на которую вы смотрите, всегда проверять ее на соответствие null, а затем делать «что-то», если вы сталкиваетесь с null. Проблема с таким подходом заключается в том, что вы часто глубоко погружаетесь в какое-либо дерево вызовов, и поэтому «что-то», что вы делаете (например, возвращаете «»»разумное»»» значение по умолчанию), скорее всего, не только является нарушением многоуровневости, но и, скорее всего, замалчивает проблему, а не вызывает ее решение. В этих случаях на самом деле, вероятно, лучше позволить генерировать исключение NullReferenceException, а не предпринимать какие-то половинчатые попытки продолжить.
Разумнее всего установить соглашение о кодировании, в котором ваши ссылки никогда не будут нулевыми, за исключением нескольких случаев, когда из контекста очевидно, что они могут быть нулевыми. Кроме того, и везде, где это возможно, можно сделать свои классы неизменяемыми или в основном неизменяемыми, чтобы все инварианты можно было выполнять в конструкторе, а остальной код мог продолжать свою жизнь. Например, я мог бы написать:
public class Person {
private readonly string firstName;
private readonly string lastName;
private readonly Nubbin optionalNubbin;
}
…где из названия ясно, что optionalNubbin может иметь значение null.
Последний и наиболее радикальный подход заключается в написании кода, который не допускает значения null. Вы могли бы изобрести двойственное значение Nullable<T>, а именно:
public struct NonNullable<T> {
...
}
Реализация может работать несколькими различными способами (либо с использованием явного свойства Value, либо, возможно, с использованием перегрузки оператора), но в любом случае задача NonNullable — никогда не позволять кому-либо устанавливать для него значение null.
Ответ №5:
Поскольку instance1.property1
значение null никогда не должно быть, посмотрите, есть ли способ правильно его инициализировать, а затем выдать, ArgumentNullException
если кто-то попытается присвоить ему значение null.
Пример:
public string Property1
{
set
{
if(value == null)
{
throw new ArgumentNullException();
}
instance1.property1 = value;
}
}
Ответ №6:
вы можете сделать что-то вроде приведенного ниже.
public string MyString
{
get
{
if(instance!=null amp;amp; instance1.property1.Equals("bla"))
{
return "bla";
}
else
{
return String.Empty;
}
}
}
В основном это сначала проверит, является ли instance нулевым или нет.