Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Why can't I have a type Constraint of Object

Tags:

c#

.net

generics

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.

like image 917
Vaccano Avatar asked Nov 28 '12 23:11

Vaccano


People also ask

What is a type constraint C#?

A type constraint on a generic type parameter indicates a requirement that a type must fulfill in order to be accepted as a type argument for that type parameter. (For example, it might have to be a given class type or a subtype of that class type, or it might have to implement a given interface.)

Where is generic type constraint?

The where clause in a generic definition specifies constraints on the types that are used as arguments for type parameters in a generic type, method, delegate, or local function. Constraints can specify interfaces, base classes, or require a generic type to be a reference, value, or unmanaged type.

What is the purpose of the class constraint on a type parameter?

Object, you'll apply constraints to the type parameter. For example, the base class constraint tells the compiler that only objects of this type or derived from this type will be used as type arguments. Once the compiler has this guarantee, it can allow methods of that type to be called in the generic class.


2 Answers

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.

like image 126
Jon Avatar answered Sep 28 '22 00:09

Jon


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.

like image 35
Marc Gravell Avatar answered Sep 27 '22 23:09

Marc Gravell