Что может заставить это свойство время от времени исключать исключение NullReferenceException?
У меня есть класс asp.net/C#, который изменяет размеры изображений для кеширования на сервере в виде файлов, однако часть кода, которая определяет, какой кодировщик использовать, иногда бросает исключение NullReferenceException.
Вот код, который инициализирует и передает обратно кодеры:
public static class ImageUtilities{
private static Dictionary<string, ImageCodecInfo> encoders = null;
public static Dictionary<string, ImageCodecInfo> Encoders{
get{
if (encoders == null){
encoders = new Dictionary<string, ImageCodecInfo>();
}
//if there are no codecs, try loading them
if (encoders.Count == 0){
foreach (ImageCodecInfo codec in ImageCodecInfo.GetImageEncoders()){
encoders.Add(codec.MimeType.ToLower(), codec);
}
}
return encoders;
}
}
...
Это конкретная строка, на которую распространяется исключение:
encoders.Add(codec.MimeType.ToLower(), codec);
Это текст ошибки:
Object reference not set to an instance of an object.
at System.Collections.Generic.Dictionary`2.Insert(TKey key, TValue value, Boolean add)
at System.Collections.Generic.Dictionary`2.Add(TKey key, TValue value)
Это единственное место, где вызывается свойство Encoders (а затем строка ниже этой в трассировке стека):
if (Encoders.ContainsKey(lookupKey)){
foundCodec = Encoders[lookupKey];
}
Даже если lookupKey был нулевым, не должен ли поиск просто возвращать null, а не бросать исключение?
Ответы
Ответ 1
Вы пытаетесь использовать "ленивый загруженный синглтон", но вы не принимаете concurrency. Самый простой способ сделать это, не жертвуя производительностью, - это Lazy<T>
:
private static Lazy<Dictionary<string, ImageCodecInfo>> _encoders =
new Lazy<Dictionary<string, ImageCodecInfo>>(() =>
ImageCodecInfo.GetImageEncoders().ToDictionary(x => x.MimeType.ToLower(), x => x));
public static Dictionary<string, ImageCodecInfo> Encoders
{
get { return _encoders.Value; }
}
Это шаблон # 6 Джон Скит - отличная статья о различных способах реализации этого шаблона.
Вы также можете использовать словарь, предназначенный только для чтения, для предотвращения попыток добавления каких-либо вызывающих абонентов.
private static Lazy<ReadOnlyDictionary<string, ImageCodecInfo>> _encoders =
new Lazy<ReadOnlyDictionary<string, ImageCodecInfo>>(() =>
new ReadOnlyDictionary<string, ImageCodecInfo>(
ImageCodecInfo.GetImageEncoders()
.ToDictionary(x => x.MimeType.ToLower(), x => x)));
public static IReadOnlyDictionary<string, ImageCodecInfo> Encoders
{
get { return _encoders.Value; }
}
Другой способ, которым вы можете справиться с этим, - это ConcurrentDictionary
, но это похоже на излишний, поскольку вы не будете часто добавлять элементы.
Ответ 2
Поскольку этот код находится в приложении ASP.NET, могут возникнуть некоторые проблемы с concurrency. Попробуйте создать оператор словаря lock
:
private static object _lock = new object();
public static Dictionary<string, ImageCodecInfo> Encoders{
get{
lock(_lock) {
if (encoders == null){
encoders = new Dictionary<string, ImageCodecInfo>();
}
//if there are no codecs, try loading them
if (encoders.Count == 0){
foreach (ImageCodecInfo codec in ImageCodecInfo.GetImageEncoders()){
encoders.Add(codec.MimeType.ToLower(), codec);
}
}
return encoders;
}
}
}
Обычно Dictionary
не может иметь null
ключи (потому что вызов GetHashCode()
на каждый объект, который вы ввели). Но поскольку вы вызываете .ToLower()
в MimeType - это скорее != null
(иначе исключение было бы намного раньше). Если lock
не решит проблему, которую вы можете проверить, какое значение вы действительно вставляете в словарь с помощью отладчика.
Ответ 3
Это может быть упрощено, поскольку кодеры не будут меняться каждый раз, когда вы звоните. Вот версия, которая вернет кодировщики в качестве словаря и кэширует их в локальном объекте словаря
public static Dictionary<string, ImageCodecInfo> Encoders
{
get {
return encoders ??
(encoders = ImageCodecInfo.GetImageEncoders().ToDictionary(c => c.MimeType.ToLower()));
}
}