【问题标题】:Casting with generics使用泛型进行铸造
【发布时间】:2012-05-09 05:29:31
【问题描述】:

我在尝试访问实现类的接口属性时遇到了问题。问题是,我在运行时只有特定的 (Cat) 类型,所以我的应用在尝试投射时会中断。

这是我所拥有的:

public class Animal {}
public class Cat : Animal {}

public interface IPetSitter {}
public interface IPetSitter<T> IPetSitter where T : Animal {
    T Pet { get; set; }
}

public class Kid { }

public class NeighborhoodKid : Kid, IPetSitter<Animal> {
    Animal Pet { get; set; }
}

// --- Implementation ---

// Kid Timmy is instantiated elsewhere
// Animal type "A" is passed in dynamically

if (Timmy is IPetSitter) {
    ((IPetSitter<A>)Timmy).Pet = new A();
}

如果类型不匹配,此转换将出错。我想做这样的事情:

public interface IPetSitter {
    object Pet { get; set; }
}

public interface IPetSitter<T> : IPetSitter where T : Animal {
    new T Pet { get; set; }
}

// --- Implementation ---

NeighborhoodKid Timmy = new NeighborhoodKid();
((IPetSitter)Timmy).Pet = new Cat();

但这会强制任何实现 IPetSitter 的东西同时具有 [object Pet] 和 [Cat Pet] 属性。

我会很感激任何想法。谢谢。

更新:我最初应该更清楚地说明这一点,但有时我会创建一个 Kid 类,有时会创建一个 NeighborhoodKid 类。这就是为什么我需要转换为IPetSitter&lt;T&gt;。并非我创造的所有孩子都会坐着宠物。这听起来令人毛骨悚然。

【问题讨论】:

  • 为什么不只是Timmy.Pet = new Cat();
  • 这行不通,因为NeighborhoodKid 不一定有Pet 属性。您需要将其转换为某种 IPetSitter&lt;T&gt; 才能获得它。

标签: c# generics


【解决方案1】:

问题是你定义的

public class NeighborhoodKid : IPetSitter<Animal>
{
    Animal IPetSitter<Animal>.Pet { get; set; }
}

而不是

public class NeighborhoodKid : IPetSitter<Cat>
{
    Cat IPetSitter<Animal>.Pet { get; set; }
}

public class NeighborhoodKid<T> : IPetSitter<T> where T : Animal
{
    Cat IPetSitter<T>.Pet { get; set; }
}

【讨论】:

    【解决方案2】:

    Timmy 最终被初始化为 NeighborhoodKid。这意味着宠物,对他来说,就是动物。 Timmy 是 IPetSitter&lt;Animal&gt;,您不能将其转换为 IPetSitter&lt;Cat&gt;

    不过,你可以反过来做,假设 Cat : Animal。

    这个:

    ((IPetSitter<Animal>)Timmy).Pet = new Cat();
    

    实际上只是因为 Timmy 真的是 IPetSitter&lt;Animal&gt;,因为 NeighborhoodKid : IPetSitter&lt;Animal&gt;,所以你并没有真正对这个演员做什么 - 只是访问宠物财产。

    之后的问题在于,无法访问 Pet,也无法将 Cat 放入其中 - 将 Timmy 转换为 IPetSitter&lt;Cat&gt;,这就是问题所在。你正在把它贬低为它不是的东西。

    您始终可以向上转换,但只能向下转换为您初始化对象时使用的内容。

    如果您希望 NeighborhoodKid 成为任何类型动物(包括动物本身)的 IPetSitter,您应该这样做:

    public class NeighborhoodKid<T> : IPetSitter<T> where T : Animal
    {
        ...
    }
    

    这样,它是通用的,并且将其限制为是动物或从动物派生的东西,无论是直接还是间接。

    不过,如果您将其初始化为 new NeighborhoodKid&lt;Animal&gt;(),您将无法将其视为(又名转换为)IPetSitter&lt;Cat&gt;,因为它已初始化为 IPetSitter&lt;Animal&gt;(因为给定的通用 T 参数给 NeighborhoodKid 构造函数的是 Animal,并被传递给 IPetSitter 泛型参数)。

    【讨论】:

    • 如果我对类进行硬编码,我知道如何解决它,但我在运行时只有Cat。真的,我只想访问 Pet 属性,但我遇到了转换问题。
    • 但是Timmy 没有Pet 属性!它有一个Animal 属性!
    • 所有这些都是根据其他地方指定的类型动态发生的。有时我会实例化不是PetSitter&lt;T&gt;NeighborhoodKid。我想在适用时设置Pet 属性,但在不适用时忽略它。我应该更清楚地说明这一点。我更详细地更新了最初的问题。
    • @ChrisMontrois 这是NeighborhookKid&lt;T&gt; 中的一个简单方法。见编辑。
    • 抱歉,我没有看到您的建议如何解决问题。如果我有一个IPetSitter&lt;Animal&gt;,我将Pet 分配为new Cat(); 是合法的,但我似乎不能用泛型来做到这一点。我在运行时只有Cat 类型,所以我无法进行合法转换。
    【解决方案3】:

    为什么不只是Timmy.Pet = new Cat();? 只需将其设为 public 即可:

    public class NeighborhoodKid : Kid, IPetSitter<Animal>
    {
       public Animal Pet { get; set; }
    }
    

    如果您创建的 NeighborhoodKid 不继承自 IPetSitter,则 setter 将不可用。

    public class LazyNeighborhoodKid : Kid
    {
       // Nothing here, hes not a Pet Sitter, can access Pet
    }
    

    【讨论】:

      【解决方案4】:

      除了泛型在集合中的用处之外,我并不是真正的泛型粉丝,这就是原因。您正在强制每个 NeighborhoodKid 绑定到一种特定类型的 Animal。如果蒂米可以看猫或狗怎么办?您要为每个实例创建不同的 Timmy 实例吗?

      相反,我认为您在实例级别强制执行动物类型。例如(为简洁起见,我截断了一些类型):

      public interface IAnimal {...}
      
      public class Cat : IAnimal {...}
      
      public interface IPetSitter
      {
          IAnimal Pet { get; set; }
      }
      
      public class Kid : IPetSitter
      {
          public Kid (params Type[] allowedPets) {
              _allowedPets = allowedPets;
          }
      
          readonly IEnumerable<Type> _allowedPets;
      
          IAnimal _pet;
          public IAnimal Pet 
          {
              get {
                  return _pet;
              }
              set {
                  if (!_allowedPets.Contains(value.GetType()) {
                      throw new InvalidArgumentException("This instance does not support " + value.GetType().Name + ".");
                  }
                  _pet = value;
              }
          }
      }
      

      如果您将强制执行留在实例级别,那么您不一定需要使用具体强制转换来设置属性。

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 2023-04-05
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2018-11-17
        • 2020-01-18
        相关资源
        最近更新 更多