在C#实例方法中,’this’可以为null吗?

我有一种情况,很少有对象的队列出列空值。 对Enqueue的唯一调用是在类本身内:

m_DeltaQueue.Enqueue(this); 

很少,在以下代码中,null会从此队列中出列(静态方法):

 while (m_DeltaQueue.Count > 0 && index++ < count) if ((m = m_DeltaQueue.Dequeue()) != null) m.ProcessDelta(); else if (nullcount++ < 10) { Core.InvokeBroadcastEvent(AccessLevel.GameMaster, "A Rougue null exception was caught, m_DeltaQueue.Dequeue of a null occurred. Please inform an developer."); Console.WriteLine("m_DeltaQueue.Dequeue of a null occurred: m_DeltaQueue is not null. m_DeltaQueue.count:{0}", m_DeltaQueue.Count); } 

这是生成的错误报告:

[Jan 23 01:53:13]:m_DeltaQueue。发生null的取消:m_DeltaQueue不为null。 m_DeltaQueue.count:345

关于如何在此队列中出现空值,我感到非常困惑。

在我写这篇文章时,我想知道这是否可能是线程同步的失败; 这是一个multithreading应用程序,可能在另一个线程中同时发生入队或出队。

目前这是在.Net 4.0下,但它以前发生在3.5 / 2.0

更新:

这是我(希望是正确的)解决问题的方法,虽然下面的重要答案是同步问题,但这个问题已经明确了。

 private static object _lock = new object(); private static Queue m_DeltaQueue = new Queue(); 

排队:

  lock (_lock) m_DeltaQueue.Enqueue(this); 

出列:

  int count = m_DeltaQueue.Count; int index = 0; if (m_DeltaQueue.Count > 0 && index  0 && index++ < count) m_DeltaQueue.Dequeue().ProcessDelta(); 

我仍然试图找到适当的同步,所以任何关于正确性的评论都将非常感激。 我最初选择使用队列本身作为同步对象,因为它是私有的,并且在已经非常大的类中引入了更少的混乱。 基于John的建议,我将其更改为锁定一个新的私有静态对象_lock。

除非在手写IL中使用call指令call该方法,否则它永远不会为null。

但是,如果同时在多个线程上使用相同的Queue实例,则队列将损坏并丢失数据。

例如,如果将两个项目同时添加到近容量队列,则第二个项目可能会在第二个线程resize后将其添加到数组中,这将最终将null复制到已resize的数组并将第一个项目添加到旧arrays。

您应该使用锁保护您的队列或使用.Net 4的ConcurrentQueue

this永远不会为null(如果尝试在null上调用方法,CLR将引发exception)。 几乎可以肯定的是,您有一个同步错误,其中两个线程正在尝试同时添加到队列中。 也许两个线程都将索引递增到数组中,然后将它们的值放入相同的位置。 这意味着第一个线程正在覆盖其值。

要么同步您的访问权限(例如使用lock ),要么使用ConcurrentQueue (在.Net 4中)。

队列本身并不是线程安全的。 这是你的问题。 使用互斥锁/ lock / whatever或查找线程安全队列。

实际上,如果您使用的Queue类不是线程安全的,那么您可能同时从两个线程中出队。 避免这种情况的最简单方法是在从队列中出队时锁定队列。

 //declare this object in a globally accessible location object locker = new object(); lock(locker) { m = mDeltaQueue.Dequeue(); } 

(稍微偏离主题,极不可能的可能性;已经建立了这个社区维基。真正的问题已经解决;这主要与问题的标题有关。)

理论上,如果您的代码m_DeltaQueue.Enqueue(this)导致在参数上调用隐式转换运算符,那么确实可能导致将null引用传递给该方法。

 class Foo { public static implicit operator string(Foo foo) { return null; } void InstanceMethod() { string @this = this; if (@this == null) Console.WriteLine("Appears like 'this' is null."); } static void Main() { new Foo().InstanceMethod(); } } 

可以使用Delegate.CreateDelegate(Type, object, MethodInfo)重载创建一个在null实例上调用实例方法的Delegate.CreateDelegate(Type, object, MethodInfo)

MSDN说(强调我的)

如果firstArgument是空引用而method是实例方法,则结果取决于委托类型类型方法的签名:

  • 如果类型的签名显式包含方法的隐藏的第一个参数,则表示委托表示开放实例方法。 调用委托时,参数列表中的第一个参数将传递给方法的隐藏实例参数。
  • 如果方法类型的签名匹配(即,所有参数类型都是兼容的),则表示委托在空引用上关闭调用委托就像在null实例上调用实例方法一样 ,这不是特别有用的事情。