什么会导致此属性偶尔抛出NullReferenceException?

时间:2016-01-28 17:40:31

标签: c# asp.net iis-8.5

我有一个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,查找是否应该返回null而不是抛出异常?

3 个答案:

答案 0 :(得分:4)

您正在尝试使用“延迟加载的单例”,但您并未考虑并发性。在不牺牲性能的情况下执行此操作的最简单方法是使用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; }
}

这是Jon Skeet's excellent article on the various ways you can implement this pattern的模式#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,但这似乎有点矫枉过正,因为您不会经常添加项目。

答案 1 :(得分:1)

由于此代码在ASP.NET应用程序中,因此并发性可能存在一些问题。尝试创建字典int 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())。但是因为你在MimeType上调用.ToLower() - 它更像是!= null(否则会更早地抛出异常)。如果lock无法解决您可能需要检查的问题,那么您使用调试器实际将哪些值放入字典中。

答案 2 :(得分:0)

这可以简化,因为每次调用时编码器都不会改变。这是一个将编码器作为字典返回并将其缓存在本地字典对象中的版本

public static Dictionary<string, ImageCodecInfo> Encoders
{
    get {
        return encoders ??
               (encoders = ImageCodecInfo.GetImageEncoders().ToDictionary(c => c.MimeType.ToLower()));
    }
}