代码之家  ›  专栏  ›  技术社区  ›  buff

向基类列表添加对象

c#
  •  1
  • buff  · 技术社区  · 5 年前

    有一个基类:

    public class BaseCommand {
    
    }
    

    以及课程:

    public class CommandA : BaseCommand  {
    
    }
    public class CommandB : BaseCommand  {
    
    }
    

    BaseCommand的列表:

     IList<BaseCommand> _Requests = new
                          List<BaseCommand>()
    

    添加到此列表可以添加从BaseCommand继承的任何对象

    但如果有另一个类称为Request:

    public Request<T> where T : BaseCommand {
    }
    

    一份清单:

    IList<Request<BaseCommand>> _Requests = new
                          List<Request<BaseCommand>>();
    

    我们无法添加

     _Requests.Add(new Request<CommandA>());
    

    有谁能解释一下这种行为以及我们如何解决它? 提前谢谢

    1 回复  |  直到 5 年前
        1
  •  9
  •   Eric Lippert    5 年前

    首先,它将有助于了解您请求的功能的名称:泛型类型协方差。C#支持以下情况下的协方差:

    • 变化的类型参数是“两边”上的引用类型。也就是说,如果我们试图使用 IEnumerable<Tiger> 在这种情况下 IEnumerable<Animal> 是预期的,那么两者 Tiger Animal 必须是引用类型。
    • 泛型类型必须是接口或委托类型。
    • 泛型类型必须由该类型的作者声明为协变(或逆变),并且C编译器必须能够 证明 类型是安全的。

    你已经满足了使用方差的第一个条件,但是没有其他必要条件,所以你不能使用它。

    另一种看法是:如果C允许你做你想做的事,会出什么问题?我们换衣服吧 Request<T> Cage<T> BaseCommand 动物 ,只是为了让关系更清晰:

    abstract class Animal {}
    class Tiger : Animal {}
    class Giraffe : Animal {}
    class Cage<T> where T : Animal 
    {
      public T Contents { get; set; }
    }
    

    好吧,现在让我们看看出了什么问题:

    List<Cage<Animal>> list = new List<Cage<Animal>>(); // Clearly this must be legal
    list.Add(new Cage<Tiger>); // This is the step that you want to be legal.
    Cage<Animal> cage = list[0]; // Clearly this must be legal; list[0] is a list element.
    cage.Contents = new Giraffe(); // Clearly this is legal; a giraffe is an animal.
    

    这个程序片段有四行,其中三行必须是合法的,结果是一个类型错误:现在老虎笼子里有一只长颈鹿。因此,第二行必须是非法的,以防止类型错误。

    你可以通过制作 Request 对协方差安全的接口:

    interface IRequest<out T> where T : BaseCommand
    // out T means make this type covariant in T
    {
      T Command { get; }
      // T must appear in only *output* positions.
      // That is, no properties of type T with setters,
      // no methods that take a T as an input, and so on.
    }
    class Request<T> : IRequest<T> where T : BaseCommand
    {  implement your class here }
    ... 
    
    var list = new List<IRequest<BaseCommand>>();
    list.Add(new Request<CommandA>(new CommandA()));
    

    现在没问题了。 Request<CommandA> 可转换为 IRequest<CommandA> ,它可以协变地转换为 IRequest<BaseCommand> 可以在列表中找到。

    不像 笼子<T> , IRequest<T> 没有T型的长颈鹿,所以再也没有办法把长颈鹿放进老虎笼子里了,所以这是安全的。