简体   繁体   中英

Use a list of generic interfaces

This post is based in my original post from yesterday. But I wasn't precise enough about my needs, so I will try again here.

Please see my current code:

public interface IPosterGenerator<T> 
{
    IQueryable<T> GetPosters();
}

public class PetPosterGenerator : IPosterGenerator<PetPoster>
{
    IQueryable<PetPoster> GetPosters()
    {
        return busLogic.GetPetPosters();
    }
}

public class FlowerPosterGenerator : IPosterGenerator<FlowerPoster>
{
    IQueryable<FlowerPoster> GetPosters()
    {
        return busLogic.GetFlowerPosters();
    }
}

public class PinBoard
{
    protected List<IPosterGenerator> PosterGenerators { get; set; }  // 1. compiler error

    public PinBoard(List<IPosterGenerator> generators)  // 2. compiler error
    {
        this.PosterGenerators = generators;
    }

    public List<BasePoster> GetPosters()
    {
        var posters = new List<BasePoster>();

        foreach (var generator in PosterGenerators)
        {
            posters.Add(generator.GetPosters());
        }

        return posters;
    }
}

My goal is to create a "PinBoard" which can return a list of posters. Each poster can be of a different type (eg pet poster, flower poster, etc.). Every poster has a totally different look, content and so on. But they all inherit from a BasePoster class.

All in all I am going to have about 100 different types of posters. A concrete PinBoard instance can easily contain 1000 posters and more of all different kinds of posters (in diverse order).

In order to populate the posters of a certain PinBoard, I need to feed the PinBoard with a list of certain poster generators (the amount and type of generators will change based on the context). There will be one poster generator per poster type (eg the PetPosterGenerator which generates a collection of pet posters).

I thought it would be nice if all my poster generators could share the same (type safe) interface. That's why I introduced the IPosterGenerator interface.

My problem is that the code does not compile . There are two compiler errors with the same error message: Using the generic type 'myApp.IPosterGenerator' requires 1 type arguments

I am not surprised by the error message because I am not defining any type at these locations. It would be great if I could do something like this in the line of the first error:

protected List<IPosterGenerator<T>> PosterGenerators { get; set; }

But when I do this, the compiler can't find type or namespace T.

Now I am kind of lost. Maybe using the generic IPosterGenerator interface is not such a good idea after all. But I am sure that at some point in my application I need to know or access the concrete poster type which a certain IPosterGenerator represents.

How would you guys approach this? Thank you very much for your support in advance.

There's no magic solution. If you want to use generics, you still need to use them . I mean that you can't avoid them.

In the other hand, you can take advantage of covariance in order to let the IPosterGenerator<T> T parameter accept BasePoster :

// Check the "out" keyword in the generic parameter!
// "out" makes the T parameter covariant.
public interface IPosterGenerator<out T> where T : BasePoster
{
     IQueryable<T> GetPosters();
}

Now you can do this:

public class PinBoard
{
    protected List<IPosterGenerator<BasePoster>> PosterGenerators { get; set; }

    public PinBoard(List<IPosterGenerator<BasePoster>> generators)
    {
        this.PosterGenerators = generators;
    }

    public List<BasePoster> GetPosters()
    {
        var posters = new List<BasePoster>();

        foreach (var generator in PosterGenerators)
        {
            posters.Add(generator.GetPosters());
        }

        return posters;
    }
}

So easy! :)

You would usually define a non-generic interface and then make your generic interface inherit from it. Like so:

public interface IPosterGenerator 
{
    IQueryable GetPosters();
}

public interface IPosterGenerator<T> :
    IPosterGenerator 
{
    new IQueryable<T> GetPosters();
}

To implement this would interface you'd do something like

public class PetPosterGenerator : 
    IPosterGenerator<PetPoster>
{
    IQueryable<PetPoster> GetPosters()
    {
        return busLogic.GetPetPosters();
    }

    // Explicit interface implementation on non-generic method
    IQueryable IPosterGenerator.GetPosters()
    {
        // Invokes generic version
        return this.GetPosters();
    }
}

It is possible with generics:

public abstract class BasePoster { }
public class PetPoster : BasePoster { }
public class FlowerPoster : BasePoster { }

// NOTE the out keyword here.
public interface IPosterGenerator<out T> where T : BasePoster
{
    IQueryable<T> GetPosters();
}

public class PetPosterGenerator : IPosterGenerator<PetPoster>
{
    public IQueryable<PetPoster> GetPosters()
    {
        return Enumerable.Range(0, 5).Select(i =>
        {
            return new PetPoster();
        }).AsQueryable();
    }
}

public class FlowerPosterGenerator : IPosterGenerator<FlowerPoster>
{
    public IQueryable<FlowerPoster> GetPosters()
    {
        return Enumerable.Range(0, 5).Select(i =>
        {
            return new FlowerPoster();
        }).AsQueryable();
    }
}

public class PinBoard
{
    protected List<IPosterGenerator<BasePoster>> PosterGenerators
    {
        get;
        private set; // fixes compiler warning #1
    }

    public PinBoard(List<IPosterGenerator<BasePoster>> generators) // specify the generic type, fixes compiler warning #2
    {
        this.PosterGenerators = generators;
    }

    public List<BasePoster> GetPosters()
    {
        var posters = new List<BasePoster>();

        foreach (var generator in PosterGenerators)
        {
            posters.AddRange(generator.GetPosters()); // call AddRange not Add
        }

        return posters;
    }
}

Then this works:

var generators = new List<IPosterGenerator<BasePoster>>();
generators.Add(new FlowerPosterGenerator());
generators.Add(new PetPosterGenerator());

var pinBoard = new PinBoard(generators);

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