需要设计同时使用 "class" 和 "struct" 类型约束的泛型类



由于特殊原因,我不得不在我的应用程序中使用两种类型的字典包装器(一种用于值类型,一种用于实例类型),这些类如下:

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作为给定键的值。

另一方面,你发布的代码似乎是错误的。如果传递给索引器的valuenull,那么原始类和新"组合"实现中的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>()

调用者需要显式地检查HasValueValue,这在我看来并不比直接使用Dictionary<TKey, TValue>.TryGetValue()更方便。但是它可以工作,并且不会有当前解决方案的运行时成本。

你的问题遗漏了一点,代码是如何使用包装器实现的?很难看出这将如何成为一个广泛使用的重要策略。它太脆弱和/或支离破碎,这取决于您使用的是哪个版本的实现。假设你有一些非常特殊的场景你觉得这种方法有价值。在我看来,发布一个描述特定场景的问题,解释为什么普通的基于字典的实现不适合你,并寻求帮助解决问题,以一种不同于你目前采取的方法,会更有成效。

最新更新