Reputation: 9727
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
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
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
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
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
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