Is C# element inside Tuple cannot be upcasted?

120 Views Asked by At

I'm encountering a compilation error in C# .NET 8.0 when trying to pass a Dictionary with a tuple as its key to a method. The tuple consists of a base class and another class, but when I try to use a derived class in place of the base class within the tuple, the code fails to compile. Here's a simplified version of my code:

class A{};
class B : A {};
class X{};

void bar(Dictionary<(A sup, X oth), X> MyDicArg) {
    // Do something with Dictionary using that tuple
}

void foo() {
    var arg = new Dictionary<(B sup, X oth), X>();
    bar(arg); // Compilation error here
}

Since B is a subclass of A, I thought I could use a Dictionary with a key of (B, X) where a Dictionary with a key of (A, X) is expected due to implicit upcasting, but it does not seem to happening.

Could someone explain why this type mismatch occurs and how I might work around this issue? Is there a way to make the method accept a dictionary with a derived class in the tuple key, or do I need to adjust my approach?

2

There are 2 best solutions below

1
Mike Nakis On BEST ANSWER

The term you are looking for is covariance (And its relative, contravariance.) Unfortunately, tuples in C# are neither covariant nor contravariant, because they are structs, not interfaces, and in C# only interfaces support covariance/contravariance.

In your specific example you should consider the limitation as just fine, because if this limitation was not there, you would be allowed to potentially shoot yourself in the foot.

This is because foo() has a dictionary of a more-derived class, and you wanted it to hand-out this dictionary to bar() which would treat it as containing a less-derived class, but you are using Dictionary, not IReadOnlyDictionary, so bar() is free to add a less-derived class into the dictionary, thus wreaking havoc into foo().

0
Bartosz On

The problem you are having occurs due to the lack of covariance/contravariance support for tuple types in C#. To be more detailed:

Variance allows you to use a more derived type in place of a less derived type (covariance) or vice versa (contravariance)- unfortunately tuples don't act like this.

In your case B is derived from A, but (B, X) is not implicitly convertible to (A, X). This is because tuple types in C# do not automatically support covariance.

You could do a workaround and convert (B,X) to (A,X), for example:

void foo()
{
    var arg = new Dictionary<(B sup, X oth), X>();
    
    var convertedArg = arg.ToDictionary(entry => ((A)entry.Key.sup, entry.Key.oth), entry => entry.Value);

    bar(convertedArg);
}