0

I am developing a generic class and related generic methods. I believe the problem I am about to describe has to do with Covariance, but I can't figure out what the compiler wants me to do.

Edit: adding code which shows how I populate the dict

There is abstract base class NetworkNode. Concrete class PowerNetworkNode inherits from it. Now I have a function which will not compile. What changes should I make to get this to compile and have the desired functionality?

In abc NetworkNode, we have

public abstract IDictionary<uint, NetworkNode> 
        hydrateNodes<NetworkNode>(string nodesTableName);

In the concrete class, PowerNetworkNode, this is overridden by

public override IDictionary<uint, NetworkNode> 
        hydrateNodes<NetworkNode>(string nodesTableName)
    {
        IDictionary<uint, PowerNetworkNode> returnDict = 
             new Dictionary<uint, PowerNetworkNode>();

        // code elided
        returnDict[Convert.ToUInt32(oid)] =
             new PowerNetworkNode(oid, minPow, maxPow, edges, fuelType, ngID);
        // NetworkNode doesn't have all the fields that PowerNetworkNode has.

        return returnDict;
    }

The compiler error message is:

Error   CS0266  Cannot implicitly convert type 
'System.Collections.Generic.IDictionary<uint,  
CImodeller.Model.NetworkElements.PowerNetworkNode>' to 
'System.Collections.Generic.IDictionary<uint, NetworkNode>'. 
An explicit conversion exists (are you missing a cast?)

When I change the code to this:

public override IDictionary<uint, NetworkNode> 
        hydrateNodes<NetworkNode>(string nodesTableName)
    {
        IDictionary<uint, NetworkNode> returnDict = 
             new Dictionary<uint, PowerNetworkNode>();

        // code elided

        return returnDict;
    }

in which the line with "new Dictionary" now creates one with NetworkNode and not PowerNetworkNode, the compiler states

Error   CS0266  Cannot implicitly convert type
'System.Collections.Generic.Dictionary<uint,
CImodeller.Model.NetworkElements.PowerNetworkNode>' to
'System.Collections.Generic.IDictionary<uint, NetworkNode>'. 
An explicit conversion exists (are you missing a cast?)

I understand what this means, but I don't know what I should change to get it to work.

philologon
  • 2,093
  • 4
  • 19
  • 35
  • `IDictionary` is not variant nor by `TKey`, nor by `TValue`. – user4003407 Dec 17 '16 at 16:15
  • PetSerAI Well that answers a lot. (I'm not being sarcastic.) Is there anything define as that I can use covariantly which is also a key value pair? – philologon Dec 17 '16 at 16:18
  • Ant P, that raises a different kind of error. I have updated my question to cover what happens. – philologon Dec 17 '16 at 16:20
  • Ant P, this still just moves the error to where it says, returnDict[Convert.ToUInt32(oid)] = new PowerNetworkNode(oid, minPow, maxPow, edges, fuelType, ngID); Now it says Error CS0029 Cannot implicitly convert type 'CImodeller.Model.NetworkElements.PowerNetworkNode' to 'NetworkNode' – philologon Dec 17 '16 at 16:28
  • Is `NetworkNode` supposed to be a class? At the moment it's a generic parameter of `hydrateNodes`. How are you supposed to populate the dictionary? – Lee Dec 17 '16 at 16:28
  • @philologon that code isn't in your question. – Ant P Dec 17 '16 at 16:30
  • Lee, NetworkNode is an abstract class. PowerNetworkNode is a concrete implementation of the class. I populate the dictionary by reading a line from a csv file, creating a new instance of PowerNetworkNode, then add it to the dictionary. I think PetSerAI is on the right track. I thought IDictionary was covariant, but it isn't. – philologon Dec 17 '16 at 16:31
  • Ant P I was trying to keep unnecessary clutter out of the question for simplicity. I will add a little bit because it seems now to be necessary. – philologon Dec 17 '16 at 16:31
  • `NetworkNode` isn't a class within `hydrateNodes`, it's a generic parameter. The client can choose any type for `NetworkNode` e.g. `IDictionary d = obj.hydrateNodes()`. It looks like you need to move the generic parameter to the class instead of the `hydrateNodes` method. – Lee Dec 17 '16 at 16:32
  • Ant P, you don't have to assume it PowerNetworkNode derives from NetworkNode since I clearly state that in the OP. Is there any other key/value pair I can use that is covariant in value? – philologon Dec 17 '16 at 16:34
  • It does seem as though you have some basic misconceptions about how generics work - as Lee notes, "NetworkNode" is just the name of the generic parameter in the context of the method; that doesn't imply that the *class* `NetworkNode` is in use here - try replacing `NetworkNode` with `T` and maybe it will make the issue a little clearer. – Ant P Dec 17 '16 at 16:36
  • Yes, your first clue was the title of this question. I will see if I can do this and report back. – philologon Dec 17 '16 at 16:39
  • Ant P, that doesn't work. It goes back to the first error I reported. As I say in my Answer, I am switching to an interface which is documented as being covariant, IEnumerable – philologon Dec 17 '16 at 16:40
  • Your problem is not (just) covariance, it is that `NetworkNode` does _not_ refer to the `NetworkNode` class you have defined but to any class the client chooses. You need to remove the generic parameter from `hydrateNodes` before you can begin to fix your issue. – Lee Dec 17 '16 at 16:45
  • Ant P, that does work in the sense that it makes the issue clearer. As I say in my Answer, I am switching to an interface which is documented as being covariant, IEnumerable But my real problem is that I need a covariant key-value pair. http://stackoverflow.com/questions/13593900/how-to-get-around-lack-of-covariance-with-ireadonlydictionary – philologon Dec 17 '16 at 16:46
  • Lee, thank you. I see what you are saying. I recently learned that co and contravariant in and out modifiers can only be added to interfaces or generic delegates. I think part of my solution lies there somewhere, although I don't know exactly how yet. – philologon Dec 17 '16 at 16:50
  • Followup. I am deleting my Answer because I see it still doesn't apply to my real problem, which is I need a key value pair type of collection, but I don't see any in C# which are covariant in the Value. – philologon Dec 17 '16 at 17:12

1 Answers1

3

You have at least two misconceptions here - even before you reach your question - which is conflating the problem.

Generic type parameter names

The first issue has nothing to do with variance. Understand that this:

public override IDictionary<uint, NetworkNode> 
    hydrateNodes<NetworkNode>(string nodesTableName)
{
    IDictionary<uint, NetworkNode> returnDict = 
         new Dictionary<uint, PowerNetworkNode>();
}

Is exactly equivalent to this:

public override IDictionary<uint, NetworkNode> 
    hydrateNodes<T>(string nodesTableName)
{
    IDictionary<uint, T> returnDict = 
         new Dictionary<uint, PowerNetworkNode>();
}

That is, the generic type parameter in the method name does not refer to an actual type, it just refers to what you are going to call that type in the method. If you call a method public void DoSomething<SomeClass>, this has nothing to do with a class called SomeClass.

It should be clear from the above that you can't assign a Dictionary<uint, PowerNetworkNode> to a IDictionary<uint, T> - you have no idea whether a PowerNetworkNode is a T.

However, if you look a little more closely, you'll realise that this method doesn't need to be generic at all - you always want T to be a NetworkNode, so just...

public override IDictionary<uint, NetworkNode> 
    hydrateNodes(string nodesTableName)
{
    IDictionary<uint, NetworkNode> returnDict = 
         new Dictionary<uint, PowerNetworkNode>();
}

This is a step in the right direction but it leads to your next problem.

Variance

As you know, IDictionary<TKey, TValue> is invariant in TValue, so you can't assign a Dictionary<uint, PowerNetworkNode> to a IDictionary<uint, NetworkNode>.

Now, you've mentioned in the comments that you want "a key/value pair type that is covariant in its value."

Bad news - you can't have one of those. It's inherently a contradiction. If you declare something as Something<NetworkNode> where you can add a NetworkNode, the compiler expects you to be able to add any NetworkNode to it. If you assign a Something<PowerNetworkNode> to that - you've broken that contract. That's why invariance exists: because it is a logical necessity.

The good news is that you shouldn't need to - you can put a PowerNetworkNode in an IDictionary<uint, NetworkNode> - because a PowerNetworkNode is a NetworkNode.

So (unless you're doing something very strange in the rest of the method), the following should be fine:

public override IDictionary<uint, NetworkNode> 
    hydrateNodes(string nodesTableName)
{
    IDictionary<uint, NetworkNode> returnDict = 
         new Dictionary<uint, NetworkNode>();
}

Hopefully this has provided some insight.

Ant P
  • 24,820
  • 5
  • 68
  • 105
  • Thank you for this. After thinking it over and reading your Answer, I now see that I need to rework my architecture at a fundamental level. – philologon Dec 17 '16 at 18:33