Ferit
Ferit

Reputation: 9727

Cannot Implicitly Convert Type - Generics

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.

Upvotes: 4

Views: 1936

Answers (4)

Sefe
Sefe

Reputation: 14017

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;

        //...
    }
}

Upvotes: 5

acarlon
acarlon

Reputation: 17272

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;
        }
    }
}

Upvotes: 1

Piotr Zierhoffer
Piotr Zierhoffer

Reputation: 5151

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;

}

Upvotes: 1

Sebastian L
Sebastian L

Reputation: 834

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;
    ...
}

Upvotes: 2

Related Questions