1

Why can in the implementation class Node the generic type of IEdge<Node> not be replaced by the concrete implementation class Edge:IEdge<Node>.

// For interface definitions INode and IEdge
interface INode<TNodeN, TEdgeN>
where TNodeN : INode<TNodeN, IEdge<TNodeN>>
where TEdgeN : IEdge<TNodeN>{
void AddIncoming(TEdgeN edge); //TEdgeN used as in, (Thanks Evk - see comments!)
}

interface IEdge<TNodeE>
where TNodeE : INode<TNodeE, IEdge<TNodeE>>{}

TEdgeN is used as input parameter in void AddIncoming(TEdgeN edge) to show that in this scenario out TEdgeN does not solve the problem. Otherwise this could be solved using out TEdgeN, which would allow a covariant type TEdgeN.

// This compiles
class EdgeGood : IEdge<NodeGood>{}
class NodeGood : INode<NodeGood, IEdge<NodeGood>>{}

While the above, IEdge<NodeGood> for type TEdgeN is valid, I am wondering why EdgeBad is an invalid type TEdgeN, e.g:

//This does not compile ...
class NodeBad : INode<NodeBad,EdgeBad>{} // error type NodeBad
class EdgeBad : IEdge<NodeBad>{} // error type NodeBad 

I am fully aware, I am overseeing something, but, shouldn't:

class EdgeBad : IEdge<NodeBad>{}

be a valid type for TEdgeN in

class NodeBad : INode<TNodeN,TEdgeN>

I am sure, but assume the compiler complains, because of the cyclic generic constraints in INode and IEdge when resolving NodeBad:

  • To verify NodeBad is a valid type for TNodeN, it would need to know that EdgeBad is a valid type for TEdgeN
  • To verifiy EdgeBad is a valid type for TEdgeN, it would need to know that NodeBad is a valid type for TNodeE
  • To verify NodeBad is a valid type for TNodeE it would need that it is a valid type for TNodeN as TNodeE is used as type TNodeN in the constraint of interface IEdge
Kevin Streicher
  • 484
  • 1
  • 8
  • 25
  • 4
    This makes my head hurt. –  May 11 '18 at 10:01
  • @JayMee, because of fundamental errors, or simply because the question is badly worded? Should I maybe name the TNode, TEdge types in both interfaces differently to avoid confusion? – Kevin Streicher May 11 '18 at 10:02
  • I'm half being facetious, ignore me. I'm just struggling to build up a mental structure of this code based on those generic constraints. –  May 11 '18 at 10:03
  • @JᴀʏMᴇᴇ, actually this was a good comment and showed some flaws in the question. A good question should be clear itself, and only the answer might require some more knowledge. That you couldn't figure out which generic bounds are important where, simply told me the question was worded badly. Suggesting, I should use unique type names to avoid confusion told me, I've should have done that in the first place! Thank you :) – Kevin Streicher May 11 '18 at 10:22
  • 1
    You need to add `out` like this: `interface INode ...` for this to work – Evk May 11 '18 at 10:22
  • @Evk, which would prohibit interface methods like: `AddIncoming(TEdge edge)` in `INode` as `TEdge` is used as in parameter :) – Kevin Streicher May 11 '18 at 10:24
  • 1
    Yes, but your problem will be "solved", since code in question will compile :) At least now you know that it's not related to circular reference. – Evk May 11 '18 at 10:25
  • @Evk, you are so right :) I've added this very method just to to prohibit this solution. Tried to strip the example to the bare minimum and clearly changed the problem removing a method where `TEdgeN` is used as parameter :D – Kevin Streicher May 11 '18 at 10:29
  • 1
    Please provide a precise example of the code you are trying to use. In your question, there are two different declarations for `Node`and there are missing curly braces... – NineBerry May 11 '18 at 10:34
  • 2
    Just because two types exhibit a particular inheritance/implementation relationship (e.g. `Edge` and `IEdge`), that does *not* mean that a generic type closed over those types exhibits the *same* relationship. Most often (absent the variance modifiers), they will exhibit *no* relationship. – Damien_The_Unbeliever May 11 '18 at 10:36
  • @NineBerry, thank you for the feedback. I've added the missing curly braces and split the code blocks to make it more clear, that I am trying to understand why `class Node : INode` is invalid, while `class Node : INode>` is valid. @Damien_The_Unbeliever, could you go a bit into details? It feels like this is the exact answer, but I can not wrap my head fully around it. – Kevin Streicher May 11 '18 at 10:42
  • 1
    @NoxMortem: Consider a `List`. You might expect that to be usable as an `IList`, but it isn't - and for good reason. If you use `IList fruitBasket = new List(); fruitBasket.Add(new Apple());` then you're trying to add an apple to a bunch of bananas, and that doesn't work. – Jon Skeet May 11 '18 at 10:54
  • @JonSkeet, oh... god. Yeah. Thank you so much for ELI5 this. Thank you for all your answers here on SO. Your answers on c# covariance and contravariance essentially are the reason I was experimenting with the above example in the first place. I simply could not make the connection, that this is the very same as your example, but now it seems clearer. – Kevin Streicher May 11 '18 at 10:57

0 Answers0