由于特殊原因,我不得不在我的应用程序中使用两种类型的字典包装器(一种用于值类型,一种用于实例类型),这些类如下:
internal class StructDictionary<K, V> where V : struct
{
public IDictionary<K, V> _dictionary = new Dictionary<K, V>();
public StructDictionary(Dictionary<K, V> dictionary)
{
_dictionary = dictionary;
}
public V? this[K key]
{
get
{
V foundValue;
return _dictionary.TryGetValue(key, out foundValue) ? foundValue : (V?)null;
}
set
{
if (!value.HasValue)
return;
_dictionary[key] = value.Value;
}
}
}
和实例类型
internal class InstanceDictionary<K, V> where V : class
{
public IDictionary<K, V> _dictionary = new Dictionary<K, V>();
public InstanceDictionary(Dictionary<K, V> dictionary)
{
_dictionary = dictionary;
}
public V this[K key]
{
get
{
V foundValue;
return _dictionary.TryGetValue(key, out foundValue) ? foundValue : null;
}
set
{
if (value == null)
return;
_dictionary[key] = value;
}
}
}
这两个类的用法如下:
void Main()
{
var structDictionary = new StructDictionary<string, double>(new Dictionary<string, double>(){{"key1",1}});
var instanceDictionary = new InstanceDictionary<string, string>(new Dictionary<string, string>(){{"key1","value1"}});
structDictionary["key1"] = 70;
instanceDictionary["key1"] = "NEW_VAL";
}
由于两个字典包装器中的逻辑实际上是相同的,我想用一个类替换这两个类,而不影响性能,因为这些对象被广泛使用。
到目前为止,我已经找到了这个非最优解决方案,我基本上从索引器返回对象。这个解决方案并不真正工作,因为有一些装箱/拆箱发生(见下面的代码注释),它也使用转换。更改类型,这不是很快。
internal class BetterDictionary<K, V>
{
public IDictionary<K, V> _dictionary = new Dictionary<K, V>();
public BetterDictionary(Dictionary<K, V> dictionary)
{
_dictionary = dictionary;
}
public object this[K key]
{
get
{
V foundValue;
return _dictionary.TryGetValue(key, out foundValue) ? (object)foundValue /* Issue 1: boxing here when V is value type */: null;
}
set
{
if (value==null)
return;
_dictionary[key] = (V)Convert.ChangeType(value, typeof(V)); // Issue 2: slight performance hit due to ChangeType ?
// more code here
}
}
}
总之,是否有更好的解决方案(更好的BetterDictionary包装器)不影响性能,基本上我想要一个泛型对象,支持V泛型参数的值类型和实例类型。
EDIT在回答为什么我需要使用这个有点奇怪的字典包装器的问题时,这是因为我正在使用一个遗留的UI库,它有一些特殊的怪癖,我只能以这种形式绑定到带有索引器的对象,另外,我必须为不存在的字典值返回null。基本上,我是将字典数据转换为UI中的网格列,我不能修改UI,也不能修改内部字典,所以我必须使用像这样的人为包装器。
而且索引器集{}实际上运行的代码比仅仅设置字典值更多,这就是为什么我不能使用字典。
为缺少上下文而道歉,我将尝试用更多的上下文来更新示例。
你应该修复你的设计,使索引器不使用null
作为哨兵值。
首先,null
是一个完全有效的引用类型值。也许在你的代码中,它从来没有这样使用过,但无论如何,这样重载值都会让人困惑。在任何正常的IDictionary<TKey, TValue>
实现中,您都可以使用null
作为给定键的值。
另一方面,你发布的代码似乎是错误的。如果传递给索引器的value
是null
,那么原始类和新"组合"实现中的setter都将返回。但是,如果键存在于字典中,这意味着您可以将null
分配给给定的键,然后,如果您检索键的值,您将获得一个非null
的值。
至少,每个setter应该看起来像这样(以值类型版本为例):
set
{
if (!value.HasValue)
{
_dictionary.RemoveKey(key);
return;
}
_dictionary[key] = value.Value;
}
一条评论建议使用(在常规泛型字典中)default(V)
作为"未找到"的返回值,然后使用具体的Nullable<T>
作为值-类型类型参数V
。这可以工作,但它有同样的问题,使用null
作为"未找到"的哨兵使用引用类型时。也就是说,对于任何键,这实际上都是一个完全有效的字典值。使用它来表示"未找到"值会使实现变得混乱,并且与任何其他字典实现不一致:null
值对于键是不合法的;要删除一个键,你需要将它的值设置为不合法的null
值(这只会让事情更加混乱;它不是一个合法的值,但它仍然是你必须分配给索引器的东西)。
根据设计,在处理值类型时使用null
的选项非常有限。您可以使用box(这会产生相应的成本),或者您可以使用Nullable<T>
(它与任何引用类型都不兼容)。
如果你真的必须这样做,我建议你编写自己的Nullable<T>
版本,允许引用类型:
struct NullableEx<T>
{
private bool _hasValue;
private T _value;
public NullableEx(T value)
{
_hasValue = true;
_value = value;
}
public bool HasValue { get { return _hasValue; } }
public T Value { get { return _value; } }
// You can also e.g. add implicit operators to convert
// between T and NullableEx<T>, to implement equality
// and hash code operations, etc.
}
然后你可以在你的类中设置索引器的类型为NullableEx<V>
。当键不存在时,它可以返回new NullableEx<V>()
。
调用者需要显式地检查HasValue
和Value
,这在我看来并不比直接使用Dictionary<TKey, TValue>.TryGetValue()
更方便。但是它可以工作,并且不会有当前解决方案的运行时成本。
你的问题遗漏了一点,代码是如何使用包装器实现的?很难看出这将如何成为一个广泛使用的重要策略。它太脆弱和/或支离破碎,这取决于您使用的是哪个版本的实现。假设你有一些非常特殊的场景你觉得这种方法有价值。在我看来,发布一个描述特定场景的问题,解释为什么普通的基于字典的实现不适合你,并寻求帮助解决问题,以一种不同于你目前采取的方法,会更有成效。