类型参数必须逆向有效

Type parameter must be contravariantly valid

提问人:Nick Farsi 提问时间:7/22/2022 最后编辑:Nick Farsi 更新时间:8/5/2022 访问量:194

问:

考虑一只鸟:

public interface IBird
{

}

public class Duck : IBird
{
    public string DuckyProperty { get; set; } //this property is Duck specific!
}

还有一个鸟类处理器:

public interface IBirdProcessor<out T> where T : IBird
{
    double GetBirdWeight(T obj); //this does not compile
}

public class DuckProcessor : IBirdProcessor<Duck>
{
    public double GetBirdWeight(Duck duck)
    {
        return double.Parse(duck.DuckyProperty);
    }
}

还有一家工厂得到一个鸟类处理器:

public class BirdProcessorFactory
{
    public IBirdProcessor<T> GetBirdProcessor<T>(T obj) where T : IBird
    {
        return (IBirdProcessor<T>)new DuckProcessor();
    }
}

启动:

static void Main()
{
    var bird = new Duck() { DuckyProperty = "23" } as IBird; //the cast is necessary to simulate a real world scenario
    var factory = new BirdProcessorFactory();
    var provider = factory.GetBirdProcessor(bird);

    var weight = provider.GetBirdWeight(bird);
}

我想要一个抽象的通用鸟工厂,但我收到以下错误:

The type parameter 'T' must be contravariantly valid on 'Program.IBirdProcessor<T>.GetBirdWeight(T)'. 'T' is covariant

如果我删除“out”关键字,那么我会得到:

System.InvalidCastException: 'Unable to cast object of type 'DuckProcessor' to type 'IBirdProcessor`1[NotVegetables.Program+IBird]'.'

演员表不起作用!

作为一种解决方法,我可以这样做:

public class DuckProcessor : IBirdProcessor<IBird>
{
    public double GetBirdWeight(IBird bird)
    {
        var duck = bird as Duck;
        return double.Parse(duck.DuckyProperty);
    }
}

但这完全违背了泛型的使用,我觉得 DuckProcessor 应该与鸭子一起工作,而不是与抽象的鸟类一起工作。

我做错了什么吗?

C# 泛型 接口 转换 工厂

评论

0赞 Rand Random 7/22/2022
@Fildor - 仍然不起作用:dotnetfiddle.net/jR2Reh
1赞 Johnathan Barclay 7/22/2022
为什么不直接添加一个属性?WeightIBird
1赞 Rand Random 7/22/2022
@NickFarsi - 但如果没有它,应该如何处理?((IBirdProcessor<IBird>)new DuckProcessor()).GetBirdWeight(new Sparrow()))DuckProcessorSparrow
1赞 Rand Random 7/22/2022
@NickFarsi - 这没有回答我的问题,我想知道应该如何处理 a,因为您希望能够强制转换为它需要能够处理所有继承的类,您最终不会得到显式的.DuckProcessorSparowDuckProcessorIBirdProcessor<IBird>IBirdIBirdProcessor<Duck>
1赞 Guru Stron 8/1/2022
“我是不是做错了什么?”- 我认为最正确的方法是只是作为(或只是获得财产)的一部分。GetBirdWeightIBirdWeight{get;}

答:

4赞 evan 7/30/2022 #1

C# 泛型中的所有类型都必须在编译时已知。只有在编译时才知道,并且工厂必须返回一个 .由于协方差不是一个选项(因为只接受 s),因此无法返回 a。IBirdIBirdProcessor<IBird>DuckProcessor.GetBirdWeightDuckDuckProcessor

你可以得到你想要的行为:

public interface IBirdProcessor
{
  double GetBirdWeight(IBird obj);
}

public abstract class BirdProcessorBase<T> : IBirdProcessor where T : IBird
{
  public double GetBirdWeight(IBird bird) => GetBirdWeightInternal((T)bird);

  protected abstract double GetBirdWeightInternal(T bird);
}

public class DuckProcessor : BirdProcessorBase<Duck>
{
  protected override double GetBirdWeightInternal(Duck duck)
  {
    return double.Parse(duck.DuckyProperty);
  }
}

public class BirdProcessorFactory
{
  public IBirdProcessor GetBirdProcessor(IBird bird)
  {
    if (bird.GetType().IsAssignableTo(typeof(Duck)))
      return new DuckProcessor();

    throw new Exception($"No processor for {bird.GetType().Name}");
  }
}

但是,如果您使用不匹配的类型调用从工厂返回的处理器,它将向您抛出异常。

0赞 Mykhailo Nohas 8/5/2022 #2

请看一下我对这个问题的解决方案:

public interface IBird
{

}

public class Duck : IBird
{
    public string DuckyProperty { get; set; } //this property is Duck specific!
}

public class Chicken : IBird
{
    public string ChickenProperty { get; set; } //this property is Chicken specific!
}

public interface IBirdProcessor<Y>
    where Y : IBird
{
    double GetBirdWeight(Y obj);
}

public class DuckProcessor : IBirdProcessor<Duck>
{

    public double GetBirdWeight(Duck duck)
    {
        return double.Parse(duck.DuckyProperty);
    }
}

public class ChickenProcessor : IBirdProcessor<Chicken>
{
    public double GetBirdWeight(Chicken chicken)
    {
        return double.Parse(chicken.ChickenProperty);
    }
}

public class BirdProcessorFactory
{
    public static IBirdProcessor<T> GetProcessor<T>(T bird)
        where T : IBird
    {
        switch (bird){
            case Chicken t1:
                return (IBirdProcessor<T>)new ChickenProcessor();
            case Duck t1:
                return (IBirdProcessor<T>)new DuckProcessor();
            default:
                throw new ArgumentException();
        }
        
    }
}

为了表明它按预期工作,我添加了额外的 .ChickenProcessor

执行如下所示:

Duck duck = new Duck() { DuckyProperty = "23" };
Chicken chicken = new Chicken() { ChickenProperty = "25" };

var duckProvider = BirdProcessorFactory.GetProcessor(duck);
var chickenProvider = BirdProcessorFactory.GetProcessor(chicken);

duckProvider.GetBirdWeight(duck).Dump();
chickenProvider.GetBirdWeight(chicken).Dump();

结果符合预期:

enter image description here

评论

0赞 Nick Farsi 8/5/2022
问题是,您在运行时知道类型,而工厂方法应该使用泛型(或抽象)类型并接受任何鸟。所以实际上你完全从问题中删除了工厂。
0赞 Mykhailo Nohas 8/5/2022
@NickFarsi 感谢您的解释,因此您基本上需要一种工厂方法,它将采用正确的处理器。我调整了代码。
0赞 Nick Farsi 8/6/2022
将 DuckProcessor 转换为 IBirdProcessor<IBird> 将导致运行时异常,这与我的原始示例非常相似