What would cause this property to occasionally thr

2019-07-18 12:06发布

问题:

I have an asp.net/C# class that resizes images for caching on the server as files, however the portion of the code that determines which encoder to use seems to occasionally throw a NullReferenceException.

Here is the code which initializes and passes back the encoders:

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;
        }
    }
    ...

This is the specific line the exception is being thrown on:

encoders.Add(codec.MimeType.ToLower(), codec);

This is the error text:

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)

This is the only place where the Encoders property is called (and subsequently is the line below that one in the stack trace):

if (Encoders.ContainsKey(lookupKey)){
    foundCodec = Encoders[lookupKey];
}

Even if lookupKey were null, shouldn't the lookup just return null rather than throwing an exception?

回答1:

You're attempting to use a "lazy loaded singleton", but you're not taking concurrency into account. The easiest way to do this without sacrificing performance is with 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; }
}

This is pattern #6 of Jon Skeet's excellent article on the various ways you can implement this pattern.

You might also consider using a read-only dictionary, to prevent any callers from trying to add to it.

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

Another way you might handle this is with a ConcurrentDictionary, but that seems like overkill since you're not going to be adding items frequently.



回答2:

Since this code is in ASP.NET app, there may be some issues with concurrency. Try put creating dictionary int lock statement:

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

Generally Dictionary cannot have null keys (because the call GetHashCode() on every object you put in). But because you call .ToLower() on MimeType - it is rather != null (otherwise exception would be throw much earlier). If lock doesn't solve the issue you may want to check, what value you actually put into dictionary using debugger.



回答3:

This can be simplified since the encoders are not going to change each time you call. Here is a version that will return the encoders as a dictionary and cache them in your local dictionary object

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