C#null coalescing运算符返回null

最近,我的同事向我展示了一段无效的代码块:

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

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

问题是_categories为null 。 在调试器中,在_categories = GetCategories() ?? new List()上设置断点_categories = GetCategories() ?? new List() _categories = GetCategories() ?? new List() ,然后踩到DoSomethingElse(),_ categories仍然是null。

直接将_categories设置为GetCategories()工作正常。 拆分? 在一个完整的if语句工作正常。 空合并运算符没有。

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

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

编辑:

只是为了增加奇异性,除了初始化类之外_categories永远不会设置在任何地方

确切的代码是这样的:

 public class CategoryListControl { private ICategoryRepository _repo; private IList _categories; public override string Render(/* args */) { _repo = ServiceLocator.Get(); Category category = _repo.FindByUrl(url); _categories = _repo.GetChildren(category) ?? new List(); Render(/* Some other rendering stuff */); } } public class CategoryRepository : ICategoryRepository { private static IList _categories; public IList GetChildren(Category parent) { return _categories.Where(c => c.Parent == parent).ToList(); } } 

即使它GetChildren神奇地返回null,CategoryListControl._categories仍然永远不应该为null。 由于IEnumerable.ToList(),GetChildren也永远不会返回null。

编辑2:

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

 Category category = _repo.FindByUrl(url); _categories = _repo.GetChildren(category) ?? new List(); _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() ,_categories在if语句中为null,然后没有抛出跳过exception。

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

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

null-coalescing运算符不会被破坏。 我一直都以类似的方式使用它。 还有其他事情正在发生。

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

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

尝试做一个干净的构建。 构建菜单 – >清理,然后再次调试。 代码本身很好。

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

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

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

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

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

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

如果你没有得到“WTF ???”的exception 那么你知道错误的根源在别的地方。

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

请告诉我们您的结果。 我现在也很好奇。

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