Why can't I have a type Constraint of Object

2020-07-14 10:06发布

问题:

I am trying to setup custom IDictionary that will allow Object as my TValue.

Here is what it looks like:

public class NullTolerantDictionary<TKey, TValue> 
             : Dictionary<TKey, TValue> where TValue : class
{
    public TValue this[TKey key]
    {
        get
        {
            TValue value;
            if (TryGetValue(key, out value))
            {
                return value;
            }
            else
            {
                return DependencyProperty.UnsetValue;
            }
        }        
    }
}

When this compiles it says:

Cannot implicitly convert type 'object' to 'TValue'.

So to fix this I change my type constraint to be like this:

where TValue : object

(object instead of class)

I then get this message:

Constraint cannot be special class 'object'

How do I get around this?

Extra credit for explaining why object is not able to be a constraint.

回答1:

The problem here is not with generic type argument constraints but with the signature of the indexer.

The indexer is documented to always return a TValue, whatever that happens to be. The else branch however tries to return DependencyProperty.UnsetValue, which is of type object. Since not all objects are TValues, the compiler complaints ("no implicit conversion").

It would make no difference if you placed any number of constraints on TValue, and even if you could constrain it to object. The type argument could still be set to anything more derived than object and DependencyProperty.UnsetValue would still not be convertible to that type.

If you want to achieve such an effect you could use a public static read-only property such as

public class NullTolerantDictionary<TKey, TValue> 
         : Dictionary<TKey, TValue> where TValue : class
{
    public static TValue MissingValue { get; private set; }
}

and return that from the indexer, presumably so that it can then be used in reference comparisons.

But that would still leave open the question: how do actually get a TValue value to use as MissingValue? You could place the new() constraint on TValue and create an instance inside a static constructor, but that would decrease the possible applications for this class. This results in the design starting to get a bit clunky to use, and since that's exactly what you are trying to avoid there's not much point in going that way.



回答2:

Why is object not able to be a constraint? And how do I get around this?

You don't. And you don't need to. All managed types in .NET can be cast to object. There are a few things that can't (unmanaged pointers, etc), but they are not compatible with generics anyway.

The real problem is, I guess, that DependencyProperty.UnsetValue is object. Well.... that isn't a TValue. Imagine TValue is int or byte: there are no "unset" values of an int or byte. You cannot return an arbitrary object for such a TValue.

Frankly, I would just have your caller use TryGetValue directly, and not use this extension method. You could do something like returning a dummy class instance with a TValue, or returning null - but frankly the caller can just call TryGetValue to achieve the same thing more conveniently.



标签: c# .net generics