简体   繁体   中英

C# Function return instance of type Interface with Generic Types

I have the following:

    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>
    {

    }

I would like to be able to get the following method to cast the result:

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

I know the types are different but they implement the interfaces.

Any clue?

The only way to get this to work is if IProvider is covariant in its generic properties.

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

Doing that gets rid of your compiler error for your example code, but because you did not show what you are doing with Tin and Tout inside IProvider I can't say if it will give you other compiler errors or not.

The reason you must declare it covariant to be able to use it as a parameter is you could potentially bad things if it was not. Here is a simplified example to show how it could go wrong:

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();
    }
}

If we where allowed to return a DogCollection as a IAnimalCollection<IAnimal> that makes adding a Cat perfectly legal due to it being a IAnimal and AddAnimal would be int AddAnimal(IAnimal) .

By adding out this makes the AddAnimal method illegal, but now it is perfectly safe to say "This collection of dogs is represented as a collection of animals" because now there is no way to add to the collection an animal that is not a dog.


To make the above code compile, you must make one of two tradeoffs. Either make GetDogCollection() not return a interface and delete the addition of the 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();
    }
}

Or make the interface covariant, but to do that you would need to delete the AddAnimal method from the interface, which also would not let you add a cat to the collection.

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();
    }
}

I'm going to take the risk of explaining you an alternate approach.

Maybe I'm mistaken. It seems like you're looking to create provider instances using a factory method that will be somewhere else.

If you go this way, you can't avoid an explicit cast here:

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

Some suggestion: common naming scheme for generic parameters is a capital T and a pascal-cased identifier like TOutput .

You need this cast because C# compiler knows that TOutput must implement IOutput , and TInput must implement IInput thanks to generic constraints, and even when Provider has given generic arguments that full-fills the whole constraints, the problem comes when the factory method can't implicitly prove that generic parameters TOutput / TInput for Provider class are the same as ones given to the factory method itself :

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();
}

Check this working sample in DotNetFiddle .

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM