Что вы можете сделать по этому поводу?
Здесь есть много хороших ответов, объясняющих, что такое пустая ссылка и как ее отладить. Но очень мало о том, как предотвратить проблему или, по крайней мере, сделать ее легче поймать.
Проверить аргументы
Например, методы могут проверять разные аргументы, чтобы увидеть, null и выбросить ArgumentNullException
, исключение, явно созданное для этой конкретной цели.
Конструктор для ArgumentNullException
даже принимает имя параметра и сообщение как аргументы, чтобы вы могли точно сказать разработчику, что проблема.
public void DoSomething(MyObject obj) {
if(obj == null)
{
throw new ArgumentNullException("obj", "Need a reference to obj.");
}
}
Использовать инструменты
Есть также несколько библиотек, которые могут помочь. Например, «Resharper» может предоставить вам предупреждения во время написания кода, особенно если вы используете их атрибут: NotNullAttribute
В разделе «Контракты кода Microsoft» вы используете синтаксис, например Contract.Requires(obj != null)
, который дает вам проверку выполнения и компиляцию: Представление кодовых контрактов .
Существует также «PostSharp», который позволит вам просто использовать такие атрибуты:
public void DoSometing([NotNull] obj)
Сделав это и сделав PostSharp частью вашего процесса сборки, obj
будет проверяться на нуль во время выполнения. См. Ошибка проверки PostSharp
Решение для простого кода
Или вы всегда можете использовать свой собственный подход, используя простой старый код. Например, вот структура, которую вы можете использовать, чтобы поймать нулевые ссылки. Он моделируется после той же концепции, что и Nullable
:
[System.Diagnostics.DebuggerNonUserCode]
public struct NotNull where T: class
{
private T _value;
public T Value
{
get
{
if (_value == null)
{
throw new Exception("null value not allowed");
}
return _value;
}
set
{
if (value == null)
{
throw new Exception("null value not allowed.");
}
_value = value;
}
}
public static implicit operator T(NotNull notNullValue)
{
return notNullValue.Value;
}
public static implicit operator NotNull(T value)
{
return new NotNull { Value = value };
}
}
. Вы использовали бы очень похоже на то, как вы бы использовали Nullable
, за исключением того, что цель заключалась в том, чтобы сделать абсолютно противоположное - не разрешать null
. Вот несколько примеров:
NotNull person = null; // throws exception
NotNull person = new Person(); // OK
NotNull person = GetPerson(); // throws exception if GetPerson() returns null
NotNull
неявно отбрасывается в и из T
, поэтому вы можете использовать его в любом месте, где это необходимо. Например, вы можете передать объект Person
методу, который принимает значение NotNull
:
Person person = new Person { Name = "John" };
WriteName(person);
public static void WriteName(NotNull person)
{
Console.WriteLine(person.Value.Name);
}
Как вы можете видеть выше, как с помощью nullable, вы получите доступ к базовому значению через Value
имущество. Кроме того, вы можете использовать явный или неявный листинг, вы можете увидеть пример с возвращаемым значением ниже:
Person person = GetPerson();
public static NotNull GetPerson()
{
return new Person { Name = "John" };
}
Или вы даже можете использовать его, когда метод просто возвращает T
(в этом случае Person
), выполнив бросок. Например, следующий код будет похож на код выше:
Person person = (NotNull)GetPerson();
public static Person GetPerson()
{
return new Person { Name = "John" };
}
Объединить с Extension
Объединить NotNull
с методом расширения, и вы можете охватить еще больше ситуаций. Вот пример того, как может выглядеть метод расширения:
[System.Diagnostics.DebuggerNonUserCode]
public static class NotNullExtension
{
public static T NotNull(this T @this) where T: class
{
if (@this == null)
{
throw new Exception("null value not allowed");
}
return @this;
}
}
И вот пример того, как он может быть использован:
var person = GetPerson().NotNull();
GitHub
Для вашей справки я сделал код выше, доступный на GitHub, вы можете найти его по адресу:
https://github.com/luisperezphd/NotNull
Функция родственного языка
В C # 6.0 был введен «оператор с нулевым условием», который немного помогает в этом. С помощью этой функции вы можете ссылаться на вложенные объекты, и если какой-либо из них null
, все выражение возвращает null
.
Это уменьшает количество нулевых проверок, которые вы должны выполнять в некоторых случаях. Синтаксис заключается в том, чтобы поставить вопросительный знак перед каждой точкой. Возьмите следующий код, например:
var address = country?.State?.County?.City;
Представьте, что country
является объектом типа Country
, который имеет свойство, называемое State
и т. Д. Если country
, State
, County
или City
- null
, то address will be
null . Therefore you only have to check whether
адрес is
null`.
Это отличная функция, но она дает вам меньше информации. Это не делает очевидным, какой из 4 является нулевым.
Встроенный как Nullable?
C # имеет красивую стенографию для Nullable
, вы можете сделать что-то нулевое помещая знак вопроса после такого типа int?
.
Было бы неплохо, если бы у C # было что-то вроде структуры NotNull
выше и имела аналогичную стенографию, может быть, восклицательный знак (!), чтобы вы могли написать что-то вроде: public void WriteName(Person! person)
.
Если вам нужно какое-то подобие производительности и простоты управления, перейдите в очередь производителей-потребителей и только один файловый писатель, как это было предложено Алексом и другими. Разрешить все потоки в файле с мьютексом просто беспорядочно - каждая задержка на диск переносится непосредственно в главные функциональные возможности приложения (с дополнительным утверждением). Это особенно непримиримо с медленными сетевыми дисками, которые, как правило, уходят без предупреждения.
Если вы можете сохранить файл в качестве FileOutputStream
, вы можете заблокировать его следующим образом:
FileOutputStream file = ...
....
// Thread safe version.
void write(byte[] bytes) {
try {
boolean written = false;
do {
try {
// Lock it!
FileLock lock = file.getChannel().lock();
try {
// Write the bytes.
file.write(bytes);
written = true;
} finally {
// Release the lock.
lock.release();
}
} catch ( OverlappingFileLockException ofle ) {
try {
// Wait a bit
Thread.sleep(0);
} catch (InterruptedException ex) {
throw new InterruptedIOException ("Interrupted waiting for a file lock.");
}
}
} while (!written);
} catch (IOException ex) {
log.warn("Failed to lock " + fileName, ex);
}
}
В этом случае вы должны использовать синхронизацию. Представьте, что 2 потока (t1 и t2) одновременно открывают файл и начинают писать на него. Изменения, выполненные первым потоком, перезаписываются вторым потоком, потому что второй поток является последним, чтобы сохранить изменения в файле. Когда поток t1 записывает в файл, t2 должен ждать, пока t1 не закончит его задачу, прежде чем он сможет его открыть.
может быть проблемой синхронизации потоков?
blockquote>Да.
Есть ли способ справиться с этим?
blockquote>Да, убедитесь, что записи сериализованы путем синхронизации на соответствующем мьютексе. Или поочередно, есть только один поток, который фактически выводит в файл, и все остальные потоки просто записывают в очередь текст, который должен быть записан в очередь, из которой извлекается нить записи. (Таким образом, 20 основных потоков не блокируются при вводе-выводе.)
Повторите мьютекс: например, если все они используют один и тот же экземпляр
FileWriter
(или что-то еще), что я 'fw
, то они могут использовать его как мьютекс:synchronized (fw) { fw.write(...); }
Если каждый из них использует свой собственный
FileWriter
или что-то еще, найдите что-то еще, мьютекс.Но опять же, если поток, выполняющий ввод-вывод от имени других, вероятно, также является хорошим способом.
Я предлагаю вам организовать его так: один поток-потребитель будет потреблять все данные и записывать их в файл. Все рабочие потоки будут передавать данные в поток потребителя синхронно. Или при написании нескольких потоков файлов вы можете использовать некоторые реализации мьютексов или блокировок.
Ну, без какой-либо детали реализации, это трудно понять, но, как показывает мой тестовый пример, я всегда получаю 220 строк вывода, т. е. постоянное количество строк, с FileWriter
. Обратите внимание, что здесь не используется synchronized
.
import java.io.File;
import java.io.FileWriter;
import java.io.IOException;
/**
* Working example of synchonous, competitive writing to the same file.
* @author WesternGun
*
*/
public class ThreadCompete implements Runnable {
private FileWriter writer;
private int status;
private int counter;
private boolean stop;
private String name;
public ThreadCompete(String name) {
this.name = name;
status = 0;
stop = false;
// just open the file without appending, to clear content
try {
writer = new FileWriter(new File("test.txt"), true);
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
public static void main(String[] args) {
for (int i=0; i<20; i++) {
new Thread(new ThreadCompete("Thread" + i)).start();
}
}
private int generateRandom(int range) {
return (int) (Math.random() * range);
}
@Override
public void run() {
while (!stop) {
try {
writer = new FileWriter(new File("test.txt"), true);
if (status == 0) {
writer.write(this.name + ": Begin: " + counter);
writer.write(System.lineSeparator());
status ++;
} else if (status == 1) {
writer.write(this.name + ": Now we have " + counter + " books!");
writer.write(System.lineSeparator());
counter++;
if (counter > 8) {
status = 2;
}
} else if (status == 2) {
writer.write(this.name + ": End. " + counter);
writer.write(System.lineSeparator());
stop = true;
}
writer.flush();
writer.close();
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
}
Как я понимаю (и тест), в этом процессе есть две фазы:
Ну, это точно так же, как толпа, ожидающая снаружи ванной , без очередей .....
Итак, если ваша реализация отличается, покажите код, и мы можем помочь сломать его.