【问题标题】:Casting a generic element type downwards向下转换通用元素类型
【发布时间】:2011-02-07 16:48:50
【问题描述】:
public class ConfigControlBase<T> : UserControl
    where T : ProviderBase
{
    public T Provider { get; set; }

    public void Init(T provider)
    {
        this.Provider = provider;
    }
}


public abstract class ProviderBase
{
    public abstract ConfigControlBase<ProviderBase> GetControl();
}

public class ProviderXConfigControl : ConfigControlBase<ProviderX>
{
}

public class ProviderX : ProviderBase
{
    public override ConfigControlBase<ProviderBase> GetControl()
    {
        var confControl = new ProviderXConfigControl() as ConfigControlBase<ProviderX>;
        return confControl;
    }
}

return confControl; 抛出异常:

无法将类型ConfigControlBase&lt;ProviderX&gt; 隐式转换为ConfigControlBase&lt;ProviderBase&gt;

【问题讨论】:

    标签: c# .net generics exception-handling


    【解决方案1】:

    让我们更改类和属性的名称,但保持形状不变:

    public class Cage<T> where T : Animal
    {
        public T Contents { get; set; }
    }
    
    public class Aquarium : Cage<Fish> { }
    
    public abstract class Animal
    {
        public abstract Cage<Animal> GetCage();
    }
    
    public class Fish : Animal
    {
        public override Cage<Animal> GetCage()
        {
            return (Cage<Animal>)(new Aquarium());
        }
    }
    

    现在清楚为什么这不合法了吗?假设它是合法的。然后你可以这样做:

    Fish fish = new Fish();
    Cage<Animal> cage = fish.GetCage();
    cage.contents = new Tiger();
    

    现在你的水族馆里有一只老虎。没有人想要那样。

    编译器(或运行时)必须以某种方式防止这种类型错误;它选择尽快阻止它。它最早可以这样做是在从 Aquarium 转换为 Cage&lt;Animal&gt; 的类型测试中。编译器知道这最终会导致水族箱中的老虎,所以它根本不允许转换。如果你强制编译器允许它通过强制转换,那么它在运行时会失败。

    【讨论】:

      【解决方案2】:

      具有可赋值类型参数的泛型类型本身不可赋值。
      例如,您不能将List&lt;string&gt; 转换为List&lt;object&gt;,尽管stringobject

      为什么不支持这种强制转换还不是很明显,所以让我举个例子:

      var words = new List<string> { "Serve God", "love me", "mend" };
      var objects = (List<object>) words; // C# compiler wouldn't allow this
      objects.Add (new Car()); // we just added a Car to Shakespeare's work and the universe exploded
      

      C# 不鼓励宇宙爆炸,但是从 C# 4.0 开始,实现了这个想法的轻量级版本。你看,在某些情况下这样的强制转换实际上是安全的。

      .NET 4.0 在泛型中引入了协变和逆变的概念仅适用于接口和委托,您可能需要检查一下。

      示例(在 .NET 4.0 之前不起作用):

      void HandleCollection (IEnumerable<object> collection)
      {
          // ...
      }
      
      var words = new List<string> { "Serve God", "love me", "mend" };
      
      // IEnumerable is defined as IEnumerable<out T> in .NET 4.0
      // 'out' keyword guarantees that T is only used for return values
      // and therefore client code can't explode the universe   
      
      var objects = (IEnumerable<object>) words;
      HandleCollection (objects);
      

      【讨论】:

      • 请注意,协方差不适用于值类型。您不能将 int 列表转换为 IEnumerable 因为必须为装箱分配内存,并且没有发出代码来执行此操作。您可以将字符串列表转换为 IEnumerable,因为字符串是引用类型。
      • 我将示例更改为string 以保持一致性。谢谢你的评论。
      • 当我还是个孩子的时候,当 Eric Lippert 回答你的问题时,我以为会有某种声望奖励或荣誉徽章。
      【解决方案3】:

      这是因为ConfigControlBase&lt;ProviderX&gt; 不是ConfigControlBase&lt;ProviderBase&gt;

      【讨论】:

        【解决方案4】:

        你的

         public override ConfigControlBase<ProviderBase> GetControl()
        

        不匹配

        var confControl = new ProviderXConfigControl() as ConfigControlBase<ProviderX>;
        

        【讨论】:

          【解决方案5】:

          这个答案在你的场景中可能没有用,因为你可能应该寻找另一个解决方案,但是在反思过程中我发现转换为不太通用的类型的能力非常有用,因此我写了一个解决方案为了它。然而它只适用于接口,你必须保证你只会将正确类型的对象传递给接口。

          我基本上在运行时生成一个代理类,它会为您完成所有必需的转换。它的用法如下:

          object validator;  // An object known to implement IValidation<T>.
          object toValidate; // The object which can be validated by using the validator.
          
          // Assume validator is IValidation<string> and toValidate a string.
          
          IValidation<object> validation
              = Proxy.CreateGenericInterfaceWrapper<IValidation<object>>( validator );
          
          validation.IsValid( toValidate ); // This works! No need to know about the type.
          
          // The following will throw an InvalidCastException.
          //validation.IsValid( 10 );
          

          更多信息和源代码可以找到on my blog

          【讨论】:

            猜你喜欢
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            相关资源
            最近更新 更多