最近,我的同事向我展示了一段无法正常运行的代码:
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<Category>()
上设置断点,然后单步转到DoSomethingElse(),_ 类别仍为空。
直接将_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。由于IEnumerable.ToList(),GetChildren也永远不会返回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()
时,_categories在if语句中为null,然后没有抛出异常。
所以,似乎这是一个调试器错误。
答案 0 :(得分:2)
这可能是调试器的问题,而不是代码。尝试打印出值或在使用coalesce运算符的语句后执行空检查。
答案 1 :(得分:2)
null-coalescing运算符未被破坏。我一直都以类似的方式使用它。还有其他事情正在发生。
答案 2 :(得分:1)
如果您确定因为线程问题,那么请使用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();
}
}
答案 3 :(得分:1)
尝试进行干净的构建。构建菜单 - &gt;清理,然后再次调试。代码本身很好。
答案 4 :(得分:1)
(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<Category> _categories
。 _categories
中的CategoryListControl
字段为空是没有意义的,但_categories
类中的静态CategoryRepository
看起来应该为null,这取决于你是什么意思发布。也许你对哪个字段引发错误感到困惑。我理解该行是在CategoryListControl中调用的,所以你的错误会说它在CategoryListControl类中,但实际的异常可能来自GetChildren()
方法,该方法试图从空列表中创建子列表) 。由于这些字段的名称相同,因此很容易看出它们如何混淆。通过将_categories
中的CategoryRepository
字段设置为只读初始化字段来对此进行测试。
即使CategoryRepository中的_categories字段并非总是为null,它也可能受到我解释如何修复Control类**的任何线程问题的影响**
它确保您正在调试正确的_categories字段,试试这个。
_categories = GetCategories() ?? new List<Category>();
if(_categories == null){
throw new Exception("WTF???");
}
DoSomethingElse();
如果你没有得到“WTF ???”的异常然后你就知道错误的根源在其他地方。
并且,关于Linq扩展:Where()和ToList()都不能返回null。如果任何参数为null,则两个方法都将抛出ArgumentNullException。我用反射器检查了这个。
请告诉我们您的结果。我现在也很好奇。
答案 5 :(得分:1)
这可能发生,因为您已启用优化 - 在这种情况下,只要编译器可以证明这样做不会更改结果,分配可能会延迟。当然,这在调试器中看起来很奇怪,但它完全没问题。