【问题标题】:Populating a Collection of Wrapped<A> with Wrapped<B> classes where B implements A使用 Wrapped<B> 类填充 Wrapped<A> 的集合,其中 B 实现 A
【发布时间】:2013-05-24 01:36:11
【问题描述】:

所以我有以下简单的包装类:

interface IReference<out T> where T : myAbstractBase {
    T Value { get; }
}
public class Reference<T> : IReference<T> where T : myAbstractBase
{
    private T _value = null;
    public T Value {  get { return _value; } }
}

在我的整个应用程序中,我希望收集这些 IReference&lt;someClass&gt; 对象(其中 someClass 实现 myAbstractBase

private List<Reference<shapes>> shapeList = new Collection<Reference<shapes>>();

但我希望能够将各种不同的形状添加到这个集合中。 (特别是因为形状也是抽象的)。当然,这会产生错误:

shapeList.Add( new Reference<circle>(){ radius = 2; } );

The value "Reference[circle]" is not of type "Reference[shape]" and cannot be used in this generic collection.

有什么方法可以设计我的Reference&lt;T&gt; 类,只要AB 类型,Reference&lt;A&gt; 就会被认为是Reference&lt;B&gt; 类型?

在我看来,人们在尝试使用 Nullable 等列表时会遇到同样的问题。

我已经尝试实现隐式运算符来在 Reference 和 T 之间进行转换,但我没有想到它们有什么实际用途...

public class Reference<T> ... {
    ...

    public static implicit operator Reference<T>(T value)
    {
        return new Reference<T> { _value = value, };
    }
    public static implicit operator T(Reference<T> value)
    {
        return value.Value;
    }
}

对于任何对我的意图感到好奇的人,这都是(命运多舛的)尝试为一组类实现延迟加载而不必向这些类添加更多内容的一部分。

【问题讨论】:

  • 您必须使用 ,而这不适用于您的界面。
  • @newStackExchangeInstance 你知道,我不知道为什么那个界面是这样的。它来自今天早些时候的这个问题:stackoverflow.com/questions/16795750/…,但即使将 T 定义为不变量,接口似乎也能完成它的工作。
  • 维基百科 (en.wikipedia.org/wiki/…) 说 IList&lt;T&gt; 类型的构造函数是不变的,所以没有两个列表是另一个列表的子类型。这是否意味着这是列表的失败原因?
  • 是否使用IReferences 列表而不是References 为您工作:List&lt;IReference&lt;Shape&gt;&gt; shapeList = new List&lt;IReference&lt;Shape&gt;&gt;();
  • @steaks,这不起作用,因为与我的 Reference&lt;T&gt; 隐式转换器不同,我无法为 Interface 类定义隐式转换器,所以我无法像 List&lt;IReference&lt;circle&gt;&gt; circleList.Add( new circle() )我现在可以(因为它会自动将圆圈转换为Reference&lt;Circle&gt;

标签: c# oop generics inheritance


【解决方案1】:

我改写了这个问题并在另一个帖子中得到了答案:How to cast a Generic<T> to a Generic<R> where T is a subclass of R?

诀窍是创建一个协变接口IReference&lt;out T&gt;,并始终在使用该类型的任何地方使用该接口。所以声明List&lt;IReference&lt;myAbstractBase&gt;&gt; 而不是List&lt;Reference&lt;myAbstractBase&gt;&gt;Covariance and Contravariance in Generics

【讨论】:

    【解决方案2】:

    您的问题是您无法链接用户定义的隐式转换。乍一看,您似乎应该可以从Reference&lt;Circle&gt; -> Reference&lt;Shape&gt; 通过Reference&lt;Circle&gt; -> Circle -> Shape -> Reference&lt;Shape&gt;。但是,您将使用两个用户定义的隐式强制转换。首先你要从Reference&lt;Circle&gt; -> Circleoperator T(Reference&lt;T&gt; value)。然后你会从 Shape -> Reference&lt;Shape&gt; 通过operator Reference&lt;T&gt;(T value)。您可以通过扩展 List 创建 Add 方法的重载来解决此问题。这将使您可以在Reference.Add 中显式使用用户定义的强制转换运算符之一。现在,您不必链接用户定义的隐式转换运算符。

    查看用户定义的隐式转换规范:http://msdn.microsoft.com/en-us/library/aa691302(v=vs.71).aspx

    //You can get around your inability to chain user defined implicit casts
    //by creating a ReferenceList<T> that extends List<IReference<T>>
    //and overloads the List.Add method
    public class ReferenceList<T> : List<IReference<T>> where T : MyAbstractBase
    {
        //With this overload you can accept a T.  Then explicity cast to Reference<T>
        //by using operator Reference<T>(T value)
        public void Add(T item)
        {
            base.Add((Reference<T>)item);
        }
    }
    List<Reference<Shape>> shapeList = new List<Reference<Shape>>();
    ReferenceList<Shape> shapeList2 = new ReferenceList<Shape>();
    List<IReference<Shape>> shapeList3 = new List<IReference<Shape>>();
    
    //Interesting cases that should work with the OP
    
    //Works for obvious reasons
    shapeList.Add(new Reference<Shape>());
    //Works because you're using one user defined implicit cast 
    //where the cast is operator Reference<T>(T value).
    //Shape -> Reference<Shape>
    shapeList.Add(new Shape());
    //Works because you're using one non user defined implicit cast and one user defined 
    //implicit cast where the user defined implicit cast is operator Reference<T>(T value)
    //Circle -> Shape -> Wrapper<Shape>
    shapeList.Add(new Circle());
    //Does not work because you need to chain two user defined implicit casts
    //where the implicit casts are operator T(Reference<T> value) and operator Reference<T>(T value)
    //Reference<Circle> -> Circle -> Shape -> Reference<Shape>
    //Theoretically this could work, but the C# specs state that chaining user defined
    //implicit casts is not allowed in C# (See link below)
    shapeList.Add(new Reference<Circle>());
    //This case works for similiar reasons that shapeList.Add(new Circle()).  It uses
    //only one user defined implicit cast because you're calling operator T(Reference<T> value)
    //explicitely  
    shapeList.Add(new (Circle)Reference<Circle>());
    
    //Interesting cases for ReferenceList
    
    //Works because this calls List.Add which accepts a Reference<T>
    shapeList2.Add(new Reference<Shape>());
    //Works because this calls ReferenceList.Add wich accepts a T
    shapeList2.Add(new Circle());
    //Works because this calls ReferenceList.Add wich accepts a T.
    //and Reference<Circle> can be implicitly cast to a Circle via
    //operator T(Reference<T> value).
    //Reference<Circle> -> Circle -> Shape -> Reference<Shape> where
    //the last cast is done explicitely in the ReferenceList.Add method
    //via operator Reference<T>(T value)
    shapeList2.Add(new Reference<Circle>());
    
    //Interesting cases for List<IReference<Shape>>
    
    
    //Works for obvious reasons
    shapeList3.Add(new Reference<Shape>());
    //Works because IReference is covariant.  In C# interfaces can be
    //covariant.  Classes cannot be covariant.
    shapeList3.Add(new Reference<Circle>());
    //Does not work because C# does not support user defined implicit
    //casts to interface.  In other words, you implicitly cast Shape -> Reference<Shape>
    shapeList3.Add(new Shape());
    //Doesn't work for similiar reasons to why shapeList3.Add(new Shape()) doesn't work
    shapeList3.Add(new Circle());
    

    【讨论】:

      【解决方案3】:

      我已经开始定义一个自定义集合,该集合恰好是一个 List&lt;Reference&lt;T&gt;&gt; 内部,但它似乎在做这个伎俩:

      public class ReferenceCollection<T> : ICollection<T> where T : myAbstractBase 
      {
          private List<Reference<T>> collection = new List<Reference<T>>();
      
          public IEnumerable<T> toIEnumerable()
          {
              return (IEnumerable<T>) collection.Select(r => r.Value);
          }
      
          public IEnumerator<T> GetEnumerator()
          {
              return toIEnumerable().GetEnumerator(); ;
          }
      
          #region ICollection<T> Members
          public void Add(T item)
          {
              collection.Add(item);
          }
          ...
      }
      

      现在我可以拨打后续电话(我以前无法做到):

      ReferenceCollection<shape> test = new ReferenceCollection<shape>();
      test.Add(new circle());
      

      为什么它以这种方式工作而不是另一种?我真的在做完全不同的事情吗?也许转换实际上是以相反的顺序发生的(圆形被转换为形状,然后形状被隐式转换为Resource&lt;shape&gt;,因为它被添加到内部集合中。)

      我还看不出这种方法有任何缺点。我什至可以定义隐式转换器,将ReferenceCollection&lt;T&gt; 直接转换为List&lt;T&gt;List&lt;Reference&lt;T&gt;&gt;,而无需对其进行迭代。不过,我想知道是否有一种方法可以定义原始类,以便在隐式转换之前类似地发生强制转换以避免不可强制转换的包装类型。

      【讨论】:

      • 您发布了 shapeList.Add( new Reference&lt;circle&gt;(){ radius = 2; } ) 无法解决您原来的问题。你确定shapeList.Add( new circle(){ radius = 2; } ) 没用吗?
      • @steaks 是的,它失败了,因为shapeListList&lt;IReference&lt;Shape&gt;&gt;(),而不是List&lt;Shape&gt;()。它首先将添加的圈子实例转换为IReference&lt;Circle&gt;,然后可以理解地未能将其转换为IReference&lt;Shape&gt;
      • 这最终不好 - 我不能再将 Reference 对象反序列化到类中,因为枚举器现在将其公开为形状对象的集合。 circle 可以转换为 shape 并且 shape 可以转换为 Reference 但 Reference 不能转换为 Reference 所以反序列化器崩溃。同样的问题,现在刚刚逆转。
      【解决方案4】:

      好的,这是一个黑暗中的镜头,至少应该使您的隐式运算符编译。我没有任何设置可以立即对其进行测试。不过,它应该可以工作。将此添加到 Reference 类。

      public static implicit operator Reference<myAbstractBase>(Reference<T> i)
      {
          return i;
      }
      

      注意这里缺少类型检查,所以如果 T 不是从 myAbstractBase 派生的,你可能会被淘汰。

      【讨论】:

      • 我试图定义从 Reference&lt;T&gt;Reference&lt;myAbstractBase&gt;Reference&lt;T&gt; to Reference&lt;shape&gt;Reference&lt;T&gt; to Reference&lt;circle&gt; 的隐式转换器,但是当尝试将 Reference&lt;circle&gt; 放入 List&lt;Reference&lt;shape&gt;&gt; 时,它仍然抱怨没有能够将Reference&lt;circle&gt; 转换为Reference&lt;shape&gt;:/
      • 当您明确转换器和/或将它们添加到具有明确转换的列表时会发生什么?
      猜你喜欢
      • 1970-01-01
      • 2018-11-22
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2020-03-03
      • 2013-11-24
      • 2021-11-28
      相关资源
      最近更新 更多