【问题标题】:What would cause this property to occasionally throw a NullReferenceException?什么会导致此属性偶尔抛出 NullReferenceException?
【发布时间】:2016-01-28 17:40:30
【问题描述】:

我有一个 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 而不是抛出异常吗?

【问题讨论】:

  • 这段代码很有可能是从多个线程调用的(因为帖子包含 ASP.NET 标记),因此由于缺乏适当的同步而应该以这种方式失败。

标签: c# asp.net iis-8.5


【解决方案1】:

您正在尝试使用“延迟加载的单例”,但您没有考虑并发性。在不牺牲性能的情况下做到这一点的最简单方法是使用Lazy&lt;T&gt;

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 来处理此问题的另一种方法,但这似乎有点矫枉过正,因为您不会经常添加项目。

【讨论】:

  • 框架会用ConcurrentDictionary为你做这件事
  • @moarboilerplate - 是的,但这对于这种情况来说太过分了。 (在您发表评论时更新了我的答案。)
  • 在进一步查看问题后,您是对的-您的编辑将字典公开为只读是最好的。但是您可以重构它-只需返回_encoders.Value 应该可以工作
  • 是的,但是可以将其转换回Dictionary 并添加到其中。封装在ReadOnlyDictionary 中是防止这种情况的好习惯。虽然我想在这种情况下,它可以在延迟加载期间完成一次,而不是在属性中完成。 (任何一种都可以接受,但为了提高效率,已更新为使用这种方法。)
【解决方案2】:

由于此代码位于 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 不能解决您可能想要检查的问题,您实际上使用调试器将什么值放入字典中。

【讨论】:

    【解决方案3】:

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

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

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2016-03-24
      • 1970-01-01
      • 2010-12-05
      • 2010-09-27
      • 2014-03-07
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多