4

I have a generic class Zone<T> where T: Media.Medium. In this class I have a method public void AddNode(Node node) in which I have a statement node.ParentZone = this which raises this compiler error:

Cannot implicitly convert type 'Zone< T >' to 'Zone< Media.Medium >'

But I can't understand why as public abstract class Node has public Zone<Media.Medium> ParentZone field and class Zone<T> where T: Media.Medium is constrained by where T: Media.Medium, so T is a Media.Medium under every circumstance.

Here is the isolated code: (Full of Zone<T> and a relevant part of Node)

public class Zone<T> where T: Media.Medium
{
    public readonly Type MediaType = typeof(T);
    public readonly Guid ID = new Guid();

    private readonly List<Node> _nodes = new List<Node>();

    public void AddNode(Node node)
    {
        foreach (var port in node.GetPorts())
        {
            port.Medium = Activator.CreateInstance<T>();
        }

// Compile error in the line below

        node.ParentZone = this; 

// Cannot implicitly convert type 'Zone< T >' to 'Zone< Media.Medium >'

        _nodes.Add(node);
    }



    public List<Node> GetNodes() => new List<Node>(_nodes);

    public void RemoveNode(Node node) => _nodes.Remove(node);


}





public abstract class Node
{       
    public Zone<Media.Medium> ParentZone;

    ...
}

UPDATE #1:

My goal by this code is this: I want to add Node objects to Zone objects, Zone objects has a list of Node objects. Whenever I add a Node to a Zone, I want to set that Zone object as the parent of the Node object.

I am open to any refactoring to achieve this goal. It shouldn't have to be in this way if there is a better one.

Ferit
  • 8,692
  • 8
  • 34
  • 59
  • 2
    Did u try to make the class node also generic? public abstract class Node where T : Media.Medium { public Zone ParentZone; } – Frederik De Clercq Sep 07 '17 at 09:26
  • No I didn't. I'm going to check this, thanks. – Ferit Sep 07 '17 at 09:31
  • Well, changed `Node` to `Node where T: Media.Medium` and used `Node` instead of `Node` in class `Zone`. But I got the same compile error after refactoring. – Ferit Sep 07 '17 at 09:47
  • 1
    This is not about type constraints on generics; there are good reasons why you can't assign `Class` to `Class` - see [this](https://stackoverflow.com/a/4653199/8166486) StackOverflow answer for some insight. That said, why is your Zone type generic? Looks like you are not actually using the T type parameter anywhere. – Filip Milovanović Sep 07 '17 at 09:55
  • Thank you, gonna check that post. `Zone` is generic because I want to ensure (now or later) that every `Node` in a `Zone` has the same type of Medium which is T in `Zone`. @FilipMilovanović – Ferit Sep 07 '17 at 10:00
  • I see. Would making Node generic and changing the the add method work? `AddNode(Node node)`, where T would determine the type of the parent in Node? Or is it that you don't know the exact type when you construct/connect the objects? – Filip Milovanović Sep 07 '17 at 10:13
  • I did it (some other answers also suggested this) but it didn't change the error, still it says can't cast implicitly. And also, when I construct `Node` objects, I may or may not know in which `Medium` it operates. If I force it to `Node` it can be a problem. @FilipMilovanović – Ferit Sep 07 '17 at 10:19

4 Answers4

5

You should be clear that a generic constraint does not provide assignability between the generic types. Or expressed differently: A generic class is assignable to another class, if it inherits from the other class and shares the same type arguments. For example:

class Zone<T> where T : Medium { }

class ChildZone<T>: Zone<T>  where T : Medium { }

class Medium { }

class ChildMedium : Medium { }

What works:

Zone<Medium> mediumZone = new ChildZone<Medium>();
Zone<ChildMedium> childMediumZone = new ChildZone<ChildMedium>();

What does not work:

Zone<Medium> mediumZone = new Zone<ChildMedium>();
ChildZone<Medium> childMediumZone = new ChildZone<ChildMedium>();

Why? Because inheritance works within the generic class, not the generic arguments. Consider this:

class Zone<T> {
   T Value { get; set; }
}

An instance of Zone<Medium> can read and write items of type Medium in its Value property. That means it can read and write both, Medium and ChildMedium. In contrast, Zone<ChildMedium> can read and write only ChildMedium. The problem here is the setter, since it is impossible to assign Medium to Zone<ChildMedium>.Value, but not to to Zone<Medium>.Value. That makes the types incompatible.

If your Zone class would be an interface and you make sure that it only returns a value of type T, but you can not set a value of the type, you can use covariance:

interface IZone<out T> {
    T Value { get; }
}

If you need to read and write the value, your only choice is to make Node also generic and pass on the generic constraint:

namespace Core.Nodes
{
    public abstract class Node<T> where T : Media.Medium
    {       
        public Zone<T> ParentZone;

        //...
    }
}
Sefe
  • 13,731
  • 5
  • 42
  • 55
  • Making `Node` class also generic didn't change the error. And the reason I think is: "Because inheritance works within the generic class, not the generic arguments". Right? – Ferit Sep 07 '17 at 10:12
  • @Saibot Can you provide the code that does not work? – Sefe Sep 07 '17 at 10:19
  • No, it works. I realized that I wrote mistakenly `public Zone ParentZone` instead of `public Zone ParentZone`. Thanks. – Ferit Sep 07 '17 at 11:01
2

It looks like you got an invalid declaration of your Node class

public abstract class Node<T> where T: Media.Medium
{       
    public Zone<T> ParentZone;
    ...
}
Sebastian L
  • 838
  • 9
  • 29
  • 3
    "invalid declaration of your Node class" - it is valid. It just doesn't do what OP expected it to do. – Fildor Sep 07 '17 at 09:32
  • Well, changed Node to Node where T: Media.Medium and used Node instead of Node in class Zone. But I got the same compile error after refactoring. – Ferit Sep 07 '17 at 09:47
  • No, it works. I realized that I wrote mistakenly `public Zone ParentZone` instead of `public Zone ParentZone`. Thanks. – Ferit Sep 07 '17 at 11:01
1

I believe it's a classic covariance/contravariance problem. If you don't want to make the Node generic (maybe it doesn't have to be generic at all?) then try to fiddle with this:

https://learn.microsoft.com/en-us/dotnet/standard/generics/covariance-and-contravariance

https://learn.microsoft.com/en-us/dotnet/csharp/programming-guide/concepts/covariance-contravariance/index

If you declare specifically what you try to achieve, you may change your example and make it work. It requires adding an interface IZone.

public interface IZone<out T> where T : Media.Medium
{
    //important stuff here
}

public class Zone<T> : IZone<T> where T : Media.Medium
{
    ...
}

public abstract class Node
{
    public IZone<Media.Medium> ParentZone;

}
Piotr Zierhoffer
  • 5,005
  • 1
  • 38
  • 59
  • Gonna check this, thanks. And my intention (if it was not clear in question) is this: I want to add `Node` objects to `Zone`s, `Zone` objects has a list of `Node`s. Whenever I add a `Node` to a `Zone`, I want to set that `Zone` object as the parent of the `Node` object. – Ferit Sep 07 '17 at 09:54
1

You have said that type T inherits from Media.Medium, not Zone<T> inherits from Media.Medium. Here is a simple, hopefully illustrative example:

class Program
{
    class A<T> where T: B
    {
        public void Foo(B b)
        {
            T t = Activator.CreateInstance<T>();
            //this is OK
            b = t;
            //this is not
            b = this;
        }
    }
    class B
    {            
    }
    class C : B
    {
        public void Foo(B b)
        {
            //this is ok
            b = this;
        }
    }
}
acarlon
  • 16,764
  • 7
  • 75
  • 94
  • So you say `Activator.CreateInstance` instead of `this`. But I want the same `Zone` object of given context. For example, when I add a `Node` to a `Zone` I want to set that particular `Zone` as a parent of added `Node`. Do you think `Activator.CreateInstance` will do the job? Or will it create another object? – Ferit Sep 07 '17 at 09:51