【问题标题】:Cannot Implicitly Convert Type - Generics无法隐式转换类型 - 泛型
【发布时间】:2017-09-07 09:17:42
【问题描述】:

我有一个通用类Zone<T> where T: Media.Medium。在这个类中,我有一个方法public void AddNode(Node node),其中我有一个语句node.ParentZone = this,它会引发这个编译器错误:

无法将类型“Zone”隐式转换为 '区域'

但我不明白为什么public abstract class Nodepublic Zone<Media.Medium> ParentZone 字段和Zone<T> where T: Media.Medium 类受where T: Media.Medium 约束,所以在任何情况下T 都是Media.Medium

这里是孤立的代码:(完整的Zone<T>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>();
        }

//下面一行编译出错

        node.ParentZone = this; 

// 不能将类型 'Zone' 隐式转换为 '区域'

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

    ...
}

更新 #1:

这段代码的目标是:我想将Node 对象添加到Zone 对象,Zone 对象有一个Node 对象列表。每当我将Node 添加到Zone 时,我想将该Zone 对象设置为Node 对象的父对象。

我愿意进行任何重构以实现这一目标。如果有更好的方式,它不应该是这样的。

【问题讨论】:

  • 您是否尝试使类节点也通用?公共抽象类 Node where T : Media.Medium { public Zone ParentZone; }
  • 不,我没有。我要检查一下,谢谢。
  • 好吧,将Node 更改为Node&lt;T&gt; where T: Media.Medium 并在Zone&lt;T&gt; 类中使用Node&lt;T&gt; 而不是Node。但是重构后我得到了同样的编译错误。
  • 这与泛型的类型约束无关;您不能将Class&lt;Derived&gt; 分配给Class&lt;Base&gt; 有充分的理由 - 请参阅this StackOverflow 答案以获得一些见解。也就是说,为什么您的 Zone 类型是通用的?看起来您实际上并没有在任何地方使用 T 类型参数。
  • 谢谢你,去看看那个帖子。 Zone 是通用的,因为我想确保(现在或以后)Zone 中的每个 Node 都具有相同类型的 Medium,即 Zone&lt;T&gt; 中的 T。 @FilipMilovanović

标签: c# generics


【解决方案1】:

您应该清楚,泛型约束不提供泛型类型之间的可分配性。或者以不同的方式表达:如果泛型类继承自另一个类并共享相同类型参数,则它可以分配给另一个类。例如:

class Zone<T> where T : Medium { }

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

class Medium { }

class ChildMedium : Medium { }

什么有效:

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

什么不起作用:

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

为什么?因为继承在泛型类中起作用,而不是在泛型参数中。考虑一下:

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

Zone&lt;Medium&gt; 的实例可以在其Value 属性中读取类型为Medium 的项目。这意味着它可以读写MediumChildMedium。相比之下,Zone&lt;ChildMedium&gt; 只能读取和写入 ChildMedium。这里的问题是设置器,因为不可能将Medium 分配给Zone&lt;ChildMedium&gt;.Value,但不能分配给Zone&lt;Medium&gt;.Value。这使得类型不兼容。

如果您的Zone 类是一个接口,并且您确保它只返回一个类型为 T 的值,但您不能设置该类型的值,则可以使用covariance

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

如果你需要读取和写入值,你唯一的选择是让Node也泛型并传递泛型约束:

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

        //...
    }
}

【讨论】:

  • 使Node 类也通用并没有改变错误。我认为的原因是:“因为继承在泛型类中起作用,而不是在泛型参数中”。对吗?
  • @Saibot 能否提供不起作用的代码?
  • 不,它有效。我意识到我写错了public Zone&lt;Media.Medium&gt; ParentZone 而不是public Zone&lt;T&gt; ParentZone。谢谢。
【解决方案2】:

您的 Node 类声明似乎无效

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

【讨论】:

  • "你的 Node 类的声明无效" - 它是有效的。它只是没有做 OP 期望它做的事情。
  • 好吧,将 Node 更改为 Node,其中 T: Media.Medium 并使用 Node 而不是类 Zone 中的 Node。但是重构后我得到了同样的编译错误。
  • 不,它有效。我意识到我写错了public Zone&lt;Media.Medium&gt; ParentZone 而不是public Zone&lt;T&gt; ParentZone。谢谢。
【解决方案3】:

我相信这是一个经典的协变/逆变问题。如果你不想让 Node 通用(也许它根本不必是通用的?)然后试着摆弄这个:

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

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

如果您明确声明您要达到的目标,您可以更改您的示例并使其发挥作用。需要添加接口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;

}

【讨论】:

  • 检查一下,谢谢。我的意图(如果不清楚的话)是这样的:我想将Node 对象添加到Zones,Zone 对象有一个Nodes 列表。每当我将Node 添加到Zone 时,我想将该Zone 对象设置为Node 对象的父对象。
【解决方案4】:

你说过 T 类型继承自 Media.Medium,而不是 Zone&lt;T&gt; 继承自 Media.Medium。这是一个简单的示例:

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

【讨论】:

  • 所以你说Activator.CreateInstance&lt;T&gt;而不是this。但我想要给定上下文的相同 Zone&lt;T&gt; 对象。例如,当我将Node 添加到Zone 时,我想将该特定Zone 设置为添加的Node 的父级。你认为Activator.CreateInstance&lt;T&gt; 会完成这项工作吗?还是会创建另一个对象?
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2016-11-15
  • 1970-01-01
  • 1970-01-01
  • 2017-09-10
  • 1970-01-01
相关资源
最近更新 更多