需要设计适用于" class"的通用类。和"结构"类型约束

时间:2016-10-28 22:19:44

标签: c# generics

由于特定原因,我必须在我的应用程序中使用两种类型的字典包装器(一种用于值类型,一种用于实例类型),这些类看起来如下:

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";

}

由于2个字典包装器中的逻辑实际上是相同的,我想用只有一个类替换2个类而不影响性能,因为这些对象被广泛使用。

到目前为止,我发现这个非最佳解决方案,我基本上从索引器返回对象。这个解决方案并没有真正起作用,因为发生了一些装箱/拆箱(参见下面代码中的注释),并且它也使用了转速不快的Convert.ChangeType。

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泛型参数的值类型和实例类型的通用对象。

编辑为了回答这个问题,为什么我需要使用这个有点特殊的字典包装器呢,因为我正在处理一个遗留的UI库,它有一些特殊的怪癖我可以只绑定到具有该形式的索引器的对象,另外我必须为不存在的字典值返回null。基本上我将字典数据转换为在UI中显示为网格列,我无法修改UI,我无法修改内部字典,所以我不得不使用这样的人工包装。 / p>

同样,索引器集{}实际上运行的代码不仅仅是设置字典值,而且这也是我无法使用字典的原因。

对于缺乏上下文的抱歉,我将尝试用更多上下文更新示例。

1 个答案:

答案 0 :(得分:2)

您应该修改设计,以便索引器不会将null用作标记值。

首先,null是一个完全有效的引用类型值。也许在你的代码中,它从未以这种方式使用过,但在任何情况下都会使重载值过于混乱。在任何正常的IDictionary<TKey, TValue>实施中,您都可以使用null作为给定密钥的值。

另一方面,您发布的代码似乎错了。您原来的两个班级和新的&#34;组合的&#34;如果索引器的传递valuenull,则只需返回实现。但是,如果密钥存在于字典中,这意味着您可以将null分配给给定密钥,然后如果您检索密钥的值,则会获得非{{{} 1}}值回来。

至少,每个setter应该看起来更像这样(使用值类型版本作为示例):

null

一条评论建议使用(在常规字典中) set { if (!value.HasValue) { _dictionary.RemoveKey(key); return; } _dictionary[key] = value.Value; } 作为&#34;未找到&#34;返回值,然后使用具体的default(V)作为值类型类型参数Nullable<T>。这可能会有效,但它与使用V作为&#34;未找到&#34;的哨兵的问题相同。在使用引用类型时有。即对于任何键,它实际上通常是完全有效的字典值。用它代替一个&#34;未找到&#34;值不会使实现混淆并与任何其他字典实现不一致:null值对于密钥是不合法的;要删除密钥,您需要将其值实际设置为非合法null值(这只会让事情更加混乱......它不是一个合法的价值,但它仍然是你的东西吗?必须分配给索引器。)

按照设计,在处理值类型时使用null的选项非常有限。您可以装箱(产生费用),或者您可以使用null(与任何参考类型不兼容)。

如果您真的必须这样做,我建议您编写自己的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更方便。但它会起作用,并且不会产生当前解决方案的运行时间成本。

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