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.
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 object
s are TValue
s, 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.
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.