C# 函数返回类型为“具有泛型类型的接口”类型的实例

本文关键字:接口 类型 实例 泛型类型 返回类型 函数 | 更新日期: 2023-09-27 18:21:45

>我有以下内容:

    public interface IInput
    {
    }
    public interface IOutput
    {
    }
    public interface IProvider<Tin, Tout>
        where Tin : IInput
        where Tout : IOutput
    {
    }
    public class Input : IInput
    {
    }
    public class Output : IOutput
    {
    }
    public class Provider : IProvider<Input, Output>
    {
    }

我希望能够获得以下方法来转换结果:

private static IProvider<IInput, IOutput> GetProvider()
{
    return new Provider();
}

我知道类型不同,但它们实现了接口。

有什么线索吗?

C# 函数返回类型为“具有泛型类型的接口”类型的实例

使其工作的唯一方法是IProvider在其泛型属性中是协变的。

public interface IProvider<out Tin, out Tout>
    where Tin : IInput
    where Tout : IOutput
{
}

这样做可以消除示例代码的编译器错误,但是由于您没有显示TinToutIProvider中正在做什么,所以我不能说它是否会给您带来其他编译器错误。

您必须声明它协变才能将其用作参数的原因是,如果不是这样,您可能会遇到坏事。下面是一个简化的示例,以显示它是如何出错的:

public interface IAnimal { }
public class Dog : IAnimal { }
public class Cat : IAnimal { }
public interface IAnimalCollection<TAnimal> where TAnimal : IAnimal
{
    TAnimal GetAnimal(int i);
    int AddAnimal(TAnimal animal);
}
public class DogCollection : IAnimalCollection<Dog>
{
    private List<Dog> _dogs = new List<Dog>();
    public Dog GetAnimal(int i)
    {
        return _dogs[i];
    }
    public int AddAnimal(Dog animal)
    {
        _dogs.Add(animal);
        return _dogs.Count - 1;
    }
}
class Program
{
    static void Main(string[] args)
    {
        IAnimalCollection<IAnimal> animalCollection = GetDogCollection();
        animalCollection.AddAnimal(new Cat()); //We just added a cat to a collection of dogs.
    }
    private static IAnimalCollection<IAnimal> GetDogCollection()
    {
        return new DogCollection();
    }
}

如果我们允许将DogCollection作为IAnimalCollection<IAnimal>返回,这使得添加Cat完全合法,因为它是IAnimalAddAnimal将是int AddAnimal(IAnimal)

通过添加out这使得AddAnimal方法非法,但现在说"这个狗的集合被表示为动物的集合"是完全安全的,因为现在没有办法将不是狗的动物添加到集合中。


若要编译上述代码,必须进行以下两个权衡之一。要么不返回接口GetDogCollection()删除 cat 的添加。

class Program
{
    static void Main(string[] args)
    {
        IAnimalCollection<IAnimal> animalCollection = GetDogCollection();
        //animalCollection.AddAnimal(new Cat()); //Would get a compiler error if we tried to add a cat to the collection
    }
    private static IAnimalCollection<Dog> GetDogCollection()
    {
        return new DogCollection();
    }
}

或者使接口协变,但要做到这一点,您需要从接口中删除 AddAnimal 方法,这也不允许您将 cat 添加到集合中。

public interface IAnimalCollection<out TAnimal> where TAnimal : IAnimal
{
    TAnimal GetAnimal(int i);
    //int AddAnimal(TAnimal animal); //Can't have methods that take in the type when using "out"
}
class Program
{
    static void Main(string[] args)
    {
        IAnimalCollection<IAnimal> animalCollection = GetDogCollection();
        //animalCollection.AddAnimal(new Cat()); //Would get a compiler error because this method no longer exists.
    }
    private static IAnimalCollection<IAnimal> GetDogCollection()
    {
        return new DogCollection();
    }
}
我将

冒着风险向您解释另一种方法。

也许我错了。您似乎希望使用其他地方的工厂方法创建提供程序实例。

如果你这样做,你无法避免这里的显式强制转换:

private static IProvider<IInput, IOutput> GetProvider()
{
    // Explicit upcast
    return (IProvider<IInput, IOuput>)new Provider();
}

一些建议:泛型参数的常见命名方案是大写T和帕斯卡大小写标识符,如TOutput

你需要这个强制转换,因为 C# 编译器知道TOutput必须实现IOutput,并且由于泛型约束,TInput必须实现IInput,即使Provider给出了完全填充整个约束的泛型参数,当工厂方法无法隐式证明Provider类的泛型参数 TOutput/TInput 与提供给工厂方法本身的泛型参数相同时,问题就来了

public static IProvider<TInput, TOutput> GetProvider<TInput, TOutput>()
    where TInput : IInput 
    where TOutput : IOutput
{
    // Hey!!!!!!!! Do TOutput and TInput of this method are the same as 
    // Provider TOutput and TInput? Who knows, thus, compiler error:
    // Cannot implicitly convert type 'Provider' to 'IProvider<TInput,TOutput>'. 
    // An explicit conversion exists (are you missing a cast?) 
    return new Provider();
}

在 DotNetFiddle 中检查此工作示例。