我正在调查以下 NRE:
System.NullReferenceException: Object reference not set to an instance of an object.
at System.Collections.Generic.GenericEqualityComparer`1.GetHashCode(T obj)
at System.Collections.Generic.Dictionary`2.FindEntry(TKey key)
at System.Collections.Generic.Dictionary`2.ContainsKey(TKey key)
at UC4.Decision.Core.Event.EventObjectHeader.get_Item(String key)
...
我的EventObjectHeader
课看起来像这样:
using System;
using System.Collections;
using System.Collections.Generic;
using System.Reflection;
using System.Runtime.Serialization;
using System.Threading;
using UC4.Common.Utils.Xml;
namespace UC4.Decision.Core.Event
{
/// <summary>
/// Enumeration of the event priority.
/// </summary>
public enum EventPriority
{
/// <summary>
/// Event Priority Low
/// </summary>
Low = 0,
/// <summary>
/// Event Priority Medium (Default)
/// </summary>
Medium = 1,
/// <summary>
/// Event Priority High
/// </summary>
High = 2,
}
/// <summary>
/// Structure for the header information of an event object.
/// </summary>
/// <seealso cref="EventObject"/>
[Serializable]
public class EventObjectHeader : IEventObjectHeader, ISerializable
{
public const string KEY_TYPEURI = "typeUri";
/// <summary>
/// Fixed key which is used to store the predefined header element GUID.
/// </summary>
public const string KEY_GUID = "guid";
/// <summary>
/// Fixed key which is used to store the predefined header element IsPersistent.
/// </summary>
public const string KEY_ISPERSISTENT = "isPersistent";
/// <summary>
/// Fixed key which is used to store the time created in eventbase timezone of the Event.
/// </summary>
public const string KEY_TIME_CREATED = "timeCreated";
/// <summary>
/// Fixed key which is used to store the time created in UTC timezone of the Event.
/// </summary>
public const string KEY_TIME_CREATED_UTC = "timeCreatedUTC";
/// <summary>
/// Fixed key which is used to store the predefined header element Priority.
/// </summary>
public const string KEY_PRIORITY = "priority";
// internal data structure to store header elements as string tuples
private readonly IDictionary<string, string> _internal = new Dictionary<string, string>(6);
// lock for read and write requests to the internal dictionary
private readonly ReaderWriterLockSlim _internalDictionaryLock = new ReaderWriterLockSlim();
// caching dictionary
protected readonly Hashtable _cachedHeaderAttributes = Hashtable.Synchronized(new Hashtable());
/// <summary>
/// default header attributes (always loaded because they are columns of EB_EVENTS)
/// </summary>
public static readonly ICollection<String> DEFAULT_ATTRIBUTES = new List<string>(new[]
{
KEY_GUID, KEY_TIME_CREATED,
KEY_TIME_CREATED_UTC,KEY_TYPEURI
});
/// <summary>
/// Initializes a new instance of the <see cref="EventObjectHeader"/> class.
/// </summary>
public EventObjectHeader()
{
}
/// <summary>
/// Indicates whether the event is persistent
/// </summary>
public bool IsPersistent
{
get
{
if (ContainsKey(KEY_ISPERSISTENT) == false)
{
return false;
}
return GetValueAs<bool>(KEY_ISPERSISTENT);
}
set { SetValue(KEY_ISPERSISTENT, value); }
}
/// <summary>
/// Initializes a new instance of the <see cref="EventObjectHeader"/> class.
/// </summary>
/// <param name="eventObjectHeaderToCopy">The event object header to copy.</param>
private EventObjectHeader(IEventObjectHeader eventObjectHeaderToCopy) : this()
{
List<String> keys = new List<string>(eventObjectHeaderToCopy.Keys);
foreach (string key in keys)
{
Add(key, eventObjectHeaderToCopy[key]);
}
}
/// <summary>
/// Property for accessing the predefined header element GUID.
/// </summary>
public virtual Guid Guid
{
get
{
if (ContainsKey(KEY_GUID) == false)
{
return Guid.Empty;
}
return GetValueAs<Guid>(KEY_GUID);
}
set { SetValue(KEY_GUID, value); }
}
/// <summary>
/// Property for accessing the predefined header element LocalTimeCreated.
/// </summary>
public virtual DateTime TimeCreated
{
get
{
if (ContainsKey(KEY_TIME_CREATED) == false)
{
return default(DateTime);
}
return GetValueAs<DateTime>(KEY_TIME_CREATED);
}
set { SetValue(KEY_TIME_CREATED, value); }
}
/// <summary>
/// Property for accessing the predefined header element UTC Time created
/// </summary>
public virtual DateTime TimeCreatedUTC
{
get
{
if (ContainsKey(KEY_TIME_CREATED_UTC) == false)
{
return default(DateTime);
}
return GetValueAs<DateTime>(KEY_TIME_CREATED_UTC);
}
set { SetValue(KEY_TIME_CREATED_UTC, value); }
}
/// <summary>
/// Property for accessing the predefined header element Priority.
/// </summary>
public virtual EventPriority Priority
{
get
{
if (ContainsKey(KEY_PRIORITY) == false)
{
return EventObjectHeaderHelper.DEFAULT_PRIORITY;
}
return GetValueAs<EventPriority>(KEY_PRIORITY);
}
set { SetValue(KEY_PRIORITY, value); }
}
/// <summary>
/// Make a copy of the event object header instance.
/// </summary>
/// <returns>New instance with the same values</returns>
public virtual EventObjectHeader Copy()
{
return new EventObjectHeader(this);
}
/// <summary>
/// Clear the event object header.
/// </summary>
public virtual void Clear()
{
_internalDictionaryLock.EnterWriteLock();
try
{
_internal.Clear();
_cachedHeaderAttributes.Clear();
}
finally
{
_internalDictionaryLock.ExitWriteLock();
}
}
/// <summary>
/// Access the stored values via the key.
/// </summary>
/// <param name="key">Attribute key</param>
/// <returns>Value</returns>
public virtual string this[string key]
{
set
{
_internalDictionaryLock.EnterWriteLock();
try
{
_internal[key] = value;
if (_cachedHeaderAttributes.ContainsKey(key))
{
_cachedHeaderAttributes.Remove(key);
}
}
finally
{
_internalDictionaryLock.ExitWriteLock();
}
}
get
{
_internalDictionaryLock.EnterReadLock();
try
{
if (_internal.ContainsKey(key))
{
return _internal[key];
}
}
finally
{
_internalDictionaryLock.ExitReadLock();
}
if (key == KEY_ISPERSISTENT)
{
// this is to prevent a Exception when the IsPersistent attribute
// hadn't been set in advance
return XmlHelper.XmlConvertValueToString(false);
}
throw new EventObjectException(1167, key);
}
}
/// <summary>
/// Remove a stored value by its key.
/// If the key was not found, <code>null</code> is returned.
/// </summary>
/// <param name="key">Attribute key</param>
/// <returns>The value of the element which was removed</returns>
public virtual string Remove(string key)
{
string retVal;
_internalDictionaryLock.EnterWriteLock();
try
{
_internal.TryGetValue(key, out retVal);
_internal.Remove(key);
if (_cachedHeaderAttributes.ContainsKey(key))
{
_cachedHeaderAttributes.Remove(key);
}
}
finally
{
_internalDictionaryLock.ExitWriteLock();
}
return retVal;
}
/// <summary>
/// Get a collection of the stored keys inside the header.
/// </summary>
public virtual ICollection<string> Keys
{
get
{
_internalDictionaryLock.EnterReadLock();
try
{
return _internal.Keys;
}
finally
{
_internalDictionaryLock.ExitReadLock();
}
}
}
/// <summary>
/// Check if the header contains a specific element.
/// </summary>
/// <param name="key">Attribute key</param>
/// <returns>true, if the header contains a value for this key</returns>
public virtual bool ContainsKey(string key)
{
_internalDictionaryLock.EnterReadLock();
bool retVal;
try
{
retVal = _internal.ContainsKey(key);
}
finally
{
_internalDictionaryLock.ExitReadLock();
}
return retVal;
}
/// <summary>
/// Read only property which counts the elements in the header.
/// </summary>
public virtual int Count
{
get
{
_internalDictionaryLock.EnterReadLock();
int retVal;
try
{
retVal = _internal.Count;
}
finally
{
_internalDictionaryLock.ExitReadLock();
}
return retVal;
}
}
/// <summary>
/// Get a stored element casted to a specific type.
/// if the value is not found, the default of this type is returned.
/// </summary>
/// <typeparam name="T">Runtime type of the value</typeparam>
/// <param name="key">Attribute key</param>
/// <returns>RuntimeType safe value</returns>
public virtual T GetValueAs<T>(String key)
{
try
{
if (_cachedHeaderAttributes.ContainsKey(key))
{
Object obj = _cachedHeaderAttributes[key];
if (obj is T)
{
return (T) obj;
}
}
String value = this[key];
return XmlHelper.XmlConvertStringToValue<T>(value);
}
catch (TargetInvocationException e)
{
throw e.InnerException;
}
}
/// <summary>
/// Add a new element with a specific key.
/// </summary>
/// <param name="key">Attribute key</param>
/// <param name="value">Value</param>
public virtual void Add(String key, String value)
{
Add(key, value, false);
}
/// <summary>
/// Add a new element with a specific key.
/// </summary>
/// <typeparam name="T">Optional runtime type parameter</typeparam>
/// <param name="key">Attribute key</param>
/// <param name="value">Value</param>
public virtual void Add<T>(String key, T value)
{
Add(key, value, false);
}
/// <summary>
/// Set a value for a specific key.
/// If the key was already used, the value is overwritten and the old one is returned.
/// </summary>
/// <typeparam name="T">Optional runtime type parameter</typeparam>
/// <param name="key">Attribute key</param>
/// <param name="value">Value</param>
/// <returns>The old value if it exists</returns>
public virtual string SetValue<T>(String key, T value)
{
string retVal = null;
_internalDictionaryLock.EnterWriteLock();
try
{
if (_internal.ContainsKey(key))
{
retVal = _internal[key];
}
_internal.Remove(key);
_internal.Add(key, XmlHelper.XmlConvertValueToString(value));
_cachedHeaderAttributes[key] = value;
}
catch (TargetInvocationException e)
{
throw e.InnerException;
}
finally
{
_internalDictionaryLock.ExitWriteLock();
}
return retVal;
}
// generic add value to the header
protected virtual void Add<T>(String key, T value, bool overwrite)
{
_internalDictionaryLock.EnterWriteLock();
try
{
if (overwrite)
{
_internal.Remove(key);
}
_internal.Add(key, XmlHelper.XmlConvertValueToString(value));
_cachedHeaderAttributes[key] = value;
}
catch (TargetInvocationException e)
{
throw e.InnerException;
}
finally
{
_internalDictionaryLock.ExitWriteLock();
}
}
#region ISerializable Members
/// <summary>
/// Create a new instance from the serialization stream.
/// </summary>
/// <param name="info">Serializer information</param>
/// <param name="context">Serialization stream context</param>
protected EventObjectHeader(SerializationInfo info, StreamingContext context)
{
_internal =
info.GetValue("headerAttributes", typeof (Dictionary<string, string>)) as Dictionary<string, string>;
}
public void GetObjectData(SerializationInfo info, StreamingContext context)
{
info.AddValue("headerAttributes", _internal);
}
#endregion
}
}
对字典的所有访问_internal
都受保护ReaderWriterLockSlim
- 因此我看不到任何可能的并发问题。
使用 dotPeek,我深入Dictionary
课堂。它的ContainsKey
方法只是返回this.FindEntry(key) >= 0
,FindEntry
方法如下所示:
private int FindEntry(TKey key)
{
if ((object) key == null)
ThrowHelper.ThrowArgumentNullException(ExceptionArgument.key);
if (this.buckets != null)
{
int num = this.comparer.GetHashCode(key) & int.MaxValue;
for (int index = this.buckets[num % this.buckets.Length]; index >= 0; index = this.entries[index].next)
{
if (this.entries[index].hashCode == num && this.comparer.Equals(this.entries[index].key, key))
return index;
}
}
return -1;
}
GenericEqualityComparer
'sGetHashCode
方法看起来像这样:
public override int GetHashCode(T obj)
{
if ((object) obj == null)
return 0;
else
return obj.GetHashCode();
}
最后,我所做的只是一个简单ContainsKey
的Dictionary<string, string>
. 我对 NRE 没有任何解释,但它似乎经常在我们客户的一个生产系统中发生。什么可以解释 NullReferenceException?