简体   繁体   English

需要设计可同时使用“类”和“结构”类型约束的通用类

[英]Need to design generic class that work with both “class” and “struct” type constraints

For specific reasons I have to use 2 types of dictionary wrappers in my application (one for value types, one for instance types), the classes look as follows: 由于特定的原因,我必须在应用程序中使用两种类型的字典包装(一种用于值类型,一种用于实例类型),这些类如下所示:

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

and for instance types 和例如类型

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

These 2 classes are meant to be used as follows: 这两个类的用途如下:

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

}

Since the logic in the 2 dictionary wrappers is virtually the same I would like to replace the 2 classes with only one class without impacting performance since these objects are used extensively. 由于2个字典包装程序中的逻辑实际上是相同的,因此我想只用一个类替换这两个类, 而又不影响性能,因为这些对象被广泛使用。

So far I have found this non-optimal solution where I basically return object from the indexer. 到目前为止,我已经找到了这种非最佳解决方案,基本上可以从索引器返回对象。 This solution doesn't really work since there is some boxing/unboxing happening (see comments in the code below) and it's also using Convert.ChangeType which is not very fast. 由于存在一些装箱/拆箱操作(请参见下面的代码中的注释),并且使用的不是很快,所以此解决方案实际上不起作用。

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 
        }
    }
}

In conclusion, is there a better solution (a better BetterDictionary wrapper) that does not impact performance, basically I want a generic object that supports both value types and instance types for the V generic parameter. 总之,有没有一种不影响性能的更好的解决方案(更好的BetterDictionary包装器),基本上我想要一个通用对象,该对象同时支持V泛型参数的值类型和实例类型。

EDIT In response to the question why do I need to use this somewhat peculiar dictionary wrapper, it's because I am working with a legacy UI library that has some particular quirks where I can only bind to objects with indexers in that form, additionaly I have to return null for non existent dictionary value. 编辑为了回答为什么我需要使用这个有些奇怪的字典包装的问题,这是因为我正在使用一个具有某些特殊功能的旧式UI库,在该库中我只能绑定具有该索引器形式的对象,因此我不得不对于不存在的字典值返回null。 Basically I am transposing the dictionary data to be display as grid columns in a UI, I can't modify the UI and I can't modify the inner dictionaries, so I had to use a contrived wrapper like this. 基本上,我是将字典数据转置为在UI中显示为网格列,无法修改UI,也不能修改内部字典,因此必须使用这样的人为包装器。

Also the indexer set{} is actually running more code than just setting the dictionary value and that's why I can't use a dictionary. 另外,索引器set {}实际上正在运行更多的代码,而不仅仅是设置字典值,这就是为什么我不能使用字典的原因。

Apologies for the lack of context, I will try to update the example with more context. 对于缺乏上下文的歉意,我将尝试使用更多上下文来更新示例。

You should fix your design so that the indexer doesn't use null as a sentinel value. 您应该修复您的设计,以便索引器不会将null用作标记值。

For one, null is a perfectly valid reference type value. 首先, null是一个完全有效的引用类型值。 Maybe in your code, it's never used that way, but it's confusing to overload the value like that in any case. 也许在您的代码中,从未使用过这种方式,但是无论如何在这种情况下都会使值过载。 In any normal IDictionary<TKey, TValue> implementation, you'd be able to use null as the value for a given key. 在任何常规的IDictionary<TKey, TValue>实现中,您都可以将null用作给定键的值。

For another, the code you posted seems wrong. 另外,您发布的代码似乎是错误的。 The setters in both of your original classes and your new "combined" implementation all simply return if the passed value for the indexer is null . 在这两种原始的类和新的“组合拳”实施的制定者都简单地返回如果传递的value的索引是null But if the key exists in the dictionary, this means that you can assign null to the given key, and then later if you retrieve the key's value, you'll get a non- null value back. 但是,如果密钥存在于字典中,则意味着您可以将null分配给给定的密钥,然后,如果以后检索该密钥的值,则将获得一个非null值。

At the very least, each setter ought to look something more like this (using the value-type version as the example): 至少,每个setter都应该看起来像这样(以值类型版本为例):

    set
    {
        if (!value.HasValue)
        {
            _dictionary.RemoveKey(key);
            return;
        }

        _dictionary[key] = value.Value;
    }

One comment suggests using (in a regular generic dictionary) default(V) as the "not found" return value, and then using a concrete Nullable<T> as the value-type type parameter V . 一条评论建议使用(在常规通用词典中) default(V)作为“未找到”的返回值,然后使用具体的Nullable<T>作为值类型参数V This could work, but it has the same issues that using null as a sentinel for "not found" has when using reference types. 这可能有效,但是与使用引用类型时,将null用作“找不到”的标记存在相同的问题。 Ie that's actually normally a perfectly valid dictionary value for any key. 也就是说,实际上对于任何键来说,这通常是一个完全有效的字典值。 Using it instead to represent a "not found" value not makes the implementation confusing and inconsistent with any other dictionary implementation: null values aren't legal for a key; 用它代替表示“未找到”的值不会使实现与其他任何字典实现混淆并且不一致: null值对键不合法; and to remove a key, you need to actually set its value to the non-legal null value (which only confuses matters more…it's not a legal value, but it's still something you have to assign to the indexer). 并删除键,您实际上需要将其值设置为非合法的null值(这只会使事情更加混乱……这不是合法值,但仍然是您必须分配给索引器的值)。

By design, your options for using null when dealing with value types are very limited. 根据设计,处理值类型时使用null的选项非常有限。 You can box (incurring the cost of that), or you can use Nullable<T> (which is incompatible with any reference type). 您可以装箱(产生该费用),也可以使用Nullable<T> (与任何引用类型不兼容)。

If you really must do this, I would recommend writing your own version of Nullable<T> that allows reference types: 如果确实需要执行此操作,建议您编写自己的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.
}

Then you can make your indexer have the type NullableEx<V> in your class. 然后,可以使您的索引器在类中具有NullableEx<V>类型。 It can return new NullableEx<V>() when the key isn't present. 当键不存在时,它可以返回new NullableEx<V>()

The caller will need to check HasValue and Value explicitly, which IMHO is no more convenient than just using Dictionary<TKey, TValue>.TryGetValue() directly. 调用者将需要显式检查HasValueValue ,而IMHO并不比直接使用Dictionary<TKey, TValue>.TryGetValue()更方便。 But it would work and would not have the run-time costs your current work-around has. 但这会起作用,并且不会产生您当前解决方法所产生的运行时成本。

One significant point missing from your question is, how does code use your wrapper implementation? 您的问题遗漏的重要一点是,代码如何使用包装器实现? It's hard to see how this would be an important strategy to use broadly. 很难看出这将是广泛使用的重要策略。 It's too fragile and/or fragmented, depending on which version of your implementation you're using. 它过于脆弱和/或分散,这取决于所使用的实现版本。 So presumably you have some very specific scenario in which you feel this approach has value. 因此,假设您有一些非常特殊的场景,您认为此方法有价值。 It seems to me it would be more fruitful to post a question describing that specific scenario, explaining why the normal dictionary-based implementations don't work for you, and asking for help addressing that issue, in a manner different from the approach you're currently taking. 在我看来,发布一个描述该特定情况的问题,解释为什么普通的基于字典的实现对您不起作用以及以不同于您的方法的方式寻求帮助来解决问题,可能会更有成果目前正在服用。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM