【问题标题】:C# null coalescing operator returning nullC# null 合并运算符返回 null
【发布时间】:2011-02-01 20:06:42
【问题描述】:

最近我的同事向我展示了一段无法正常工作的代码:

public class SomeClass
{
    private IList<Category> _categories;

    public void SetCategories()
    {
        _categories = GetCategories() ?? new List<Category>();
        DoSomethingElse();
    }

    public IList<Category> GetCategories()
    {
        return RetrieveCategories().Select(Something).ToList();
    }
}

(我知道运算符是多余的,因为 linq ToList 将始终返回一个列表,但这就是代码的设置方式)。

问题在于 _categories 为 null。在调试器中,在_categories = GetCategories() ?? new List&lt;Category&gt;() 上设置断点,然后单步执行 DoSomethingElse(),_categories 仍然为空。

直接将 _categories 设置为 GetCategories() 效果很好。拆分??进入完整的 if 语句工作正常。空合并运算符没有。

这是一个 ASP.NET 应用程序,因此可能有不同的线程在干扰,但它在他的机器上,只有他连接在浏览器中。 _cateogories 不是静态的,或者任何东西。

我想知道的是,这怎么可能发生?

编辑:

更奇怪的是,_categories 从未在该函数之外的任何地方设置(除了初始化类)。

具体代码如下:

public class CategoryListControl
{
    private ICategoryRepository _repo;
    private IList<Category> _categories;

    public override string Render(/* args */)
    {
        _repo = ServiceLocator.Get<ICategoryRepository>();
        Category category = _repo.FindByUrl(url);
        _categories = _repo.GetChildren(category) ?? new List<Category>();
        Render(/* Some other rendering stuff */);
    }
}

public class CategoryRepository : ICategoryRepository
{
    private static IList<Category> _categories;

    public IList<Category> GetChildren(Category parent)
    {
        return _categories.Where(c => c.Parent == parent).ToList<Category>();
    }
}

即使 GetChildren 神奇地返回了 null,CategoryListControl._categories 仍然永远不应该为 null。 GetChildren 也不应该因为 IEnumerable.ToList() 而返回 null。

编辑 2:

试用@smartcaveman 的代码,我发现了这个:

Category category = _repo.FindByUrl(url);

_categories = _repo.GetChildren(category) ?? new List<Category>();

_skins = skin; // When the debugger is here, _categories is null

Renderer.Render(output, _skins.Content, WriteContent); // When the debugger is here, _categories is fine.

同样,在测试if(_categories == null) throw new Exception() 时,if 语句中的_categories 为空,因此没有抛出异常。

所以,这似乎是一个调试器错误。

【问题讨论】:

  • 是不是因为你在语句中实例化了?可能很好,但我以前没有这样用过。
  • 你能再检查一下吗?我在想“调试错误”。
  • 这里引人入胜的部分是GetCategories() 无论如何都无法返回null。无论如何,合并运算符是没有用的。
  • 刚刚找到this topic
  • @Pieter:是的,他知道。

标签: c# .net


【解决方案1】:

这可能是调试器的问题,而不是代码的问题。尝试在使用合并运算符的语句之后打印出值或进行空检查。

【讨论】:

  • 我相信程序首先抛出了 NullReferenceException,这导致他附加了调试器并看到了这个问题。不过,我想这仍然是可能的。
  • 我想我弄错了,抛出了 NullReferenceException。对 null 的检查将返回该值不为 null,即使当调试器直接在 if 语句上时,它显示 _categories 为 null。
【解决方案2】:

null-coalescing 操作符没有被破坏。我一直以类似的方式非常成功地使用它。正在发生其他事情。

【讨论】:

    【解决方案3】:

    如果您确定是因为线程问题,请使用 lock 关键字。我相信这应该有效。

    public class SomeClass
    {
        private IList<Category> _categories;
    
        public void SetCategories()
        {
            lock(this) 
            {
              _categories = GetCategories() ?? new List<Category>();
              DoSomethingElse();
            }
        }
    
        public IList<Category> GetCategories()
        {
            return RetrieveCategories().Select(Something).ToList();
        }
    }
    

    【讨论】:

      【解决方案4】:

      尝试进行干净的构建。 Build menu-> clean,然后再次调试。代码本身很好。

      【讨论】:

      • 嗯。这可能是因为过时的二进制文件吗? +1
      • 这是可能的,但我们确实将代码更改为手动 if 语句,该语句有效,然后返回,它再次停止工作。陈旧的二进制文件不会让调试器抱怨源不同吗?我会看看我们今天是否可以再次重现它。
      • 那么它不是陈旧的二进制文件。我假设您发布的代码是一个简化 - 我们还应该看到什么其他内容吗?
      • @Christ Shain,使用实际代码进行了编辑 - 但我仍然看不出它如何影响 _categories 为空。
      • 试试这个... 将 _categories 设置为属性,然后在设置器中执行 console.writeline 或其他一些日志记录,以了解设置的时间、线程等。如果你是调试正确,唯一可能的答案是另一个线程正在更新它的值。
      【解决方案5】:

      (1) DoSomethingElse() 可能在错误出现之前将 _categories 字段设置为 null。一种测试方法是使 _categories 字段只读。如果这是错误,那么您将收到一个编译器错误,即只读字段不能用作赋值目标。
      (2) 您的 _categories 字段是通过不同线程中的其他函数设置的。无论哪种方式,以下内容应该可以解决您的问题,或者至少弄清楚它在哪里。

      public class SomeClass
      {
          private static readonly object CategoryListLock = new object();
          private readonly List<Category> _categories = new List<Category>();
          private bool _loaded = false;
      
          public void SetCategories()
          {
              if(!_loaded)
              {
                  lock(CategoryListLock)
                  {
                      if(!_loaded)
                      {
                          _categories.AddRange(GetCategories());
                          _loaded = true;
                      }
                  }
              }
              DoSomethingElse();
          }
      
          public IList<Category> GetCategories()
          {
              return RetrieveCategories().Select(Something).ToList();
          }
      }
      

      **看到您的编辑后,您似乎有两个不同的字段IList&lt;Category&gt; _categoriesCategoryListControl 中的 _categories 字段为空是没有意义的,但是根据您发布的内容,CategoryRepository 类中的静态 _categories 看起来应该为空。也许您对哪个字段引发错误感到困惑。我知道该行是在 CategoryListControl 中调用的,因此您的错误会说它在 CategoryListControl 类中,但实际的异常可能来自 GetChildren() 方法,该方法试图从空列表中创建子列表)。由于这些字段的名称相同,因此很容易看出它们是如何混淆的。通过将CategoryRepository 中的_categories 字段设置为只读初始化字段来对此进行测试。

      即使 CategoryRepository 中的 _categories 字段并不总是为空,它也可能会受到我解释如何为 Control 类修复的任何线程问题的影响**

      为了确保您正在调试正确的 _categories 字段,请尝试此操作。

          _categories = GetCategories() ?? new List<Category>();
          if(_categories == null){
                throw new Exception("WTF???");
           }
          DoSomethingElse();
      

      如果你没有得到“WTF???”的异常那么你就知道错误的根源在别处。

      并且,关于 Linq 扩展:Where() 和 ToList() 都不能返回 null。如果任何参数为空,这两种方法都将抛出 ArgumentNullException。我用反射器检查了这个。

      请告诉我们您通过此操作获得了什么结果。我现在也很好奇。

      【讨论】:

      • 不幸的是,_categories由该函数设置。
      • @smartcaveman,我很确定它是 CategoryListControl 中的 _categories,因此会引发异常。在调试器中,我们可以毫无例外地跳过空合并运算符行,并且在下一行 CategoryListControl._categories 为空。如果 CategoryRepository._cateogries 为 null,则 null coaslescing 运算符行将在 Where() 上引发异常,因为输入不能为 null。无论哪种方式,GetCategories 中的 ToList 都不能返回 null,可以吗?
      • @smartcaveman,检查编辑。尝试您的代码似乎表明调试器存在问题。我接受了 Tim H 的回答,因为从技术上讲,我认为这是问题所在,但如果可以的话,我也会接受你的。谢谢。
      • Visual Studio 2010 附加到 w3wp.exe (iis7)
      【解决方案6】:

      这可能是因为您启用了优化 - 在这种情况下,只要编译器可以证明这样做不会改变结果,分配可能会延迟。当然,这在调试器中看起来很奇怪,但完全没问题。

      【讨论】:

        猜你喜欢
        • 2011-08-10
        • 2010-12-19
        • 1970-01-01
        • 1970-01-01
        • 2023-03-23
        • 2016-04-02
        • 2023-03-07
        • 2021-05-27
        • 1970-01-01
        相关资源
        最近更新 更多