Rand Random
Rand Random

Reputation: 7440

Generic and non-generic interfaces and inheritance - ambiguity while accessing

I have the following setup

The problem I am facing, is at that part:

public interface IApples : IFruitsBase<Apple>, IFruits
{
    void MakeAppleJuice(IEnumerable<Apple> apples);
}

Here I am getting the message:

Possible ambiguity while accessing by this Interface Apple IFruitsBase<Apple> Get() Fruit IFruitsBase<Fruit>.Get()

To fix the problem I could remove the interface IFruits from IApples, but this gives other errors as in:

public class Context
{
    public IApples Apples { get; set; }

    public Context()
    {
        this.Apples = new Apples();
    }

    public IFruits GetFruits(Type type)
    {
        return this.Apples; //simplified the code here, it should actually get the member of this that fits the type
    }
}

public class Foo
{
    public void Main()
    {
        var context = new Context();
        Check(new IFruits[] { context.Apples }); //can't do that since context.Apples doesnt inherit from IFruits, to fix the above ambiguity
    }

    public void Check(IEnumerable<IFruits> fruits)
    {
        foreach (var fruit in fruits)
            fruit.Check();
    }
}

My goal is if someone accesses the objects with:

var context = new Context();
context.Apples.[only apple relevant methods should be accessable]

if someone access it with:

var context = new Context();
context.GetFruits(item.GetType()).[only fruit relevant methods should be accessable]

and no matter if one got it with method a or b, all should be passable to methods with a signature of

void Foo(IFruits fruits);

Upvotes: 0

Views: 385

Answers (5)

Koray
Koray

Reputation: 1796

I hope I did not understand the question wrong, and this is not too stupid :)

public interface IFruit
{
}
public interface IApple: IFruit
{
}
public interface IFruits<T> where T : IFruit
{
    T GetItem();
    void ProcessItem(T fruit);
    void Check();
}
public class Fruits<T> : IFruits<T> where T : IFruit
{
    public void Check()
    {
        throw new NotImplementedException();
    }

    public T GetItem()
    {
        throw new NotImplementedException();
    }

    public void ProcessItem(T fruit)
    {
        throw new NotImplementedException();
    }
}
public interface IApples 
{
    void MakeAppleJuice(IEnumerable<IApple> apples);
}
public class Apples : Fruits<IApple>, IApples
{
    public void MakeAppleJuice(IEnumerable<IApple> apples)
    {
        throw new NotImplementedException();
    }
}
public class Context
{
    public IApples Apples { get; set; }

    public Context()
    {
        this.Apples = new Apples();
    }

    public IFruits<IFruit> GetFruits<T>()
    {
        return null;
    }
    private void test()
    {
        //this.GetFruits<IFruit>().ProcessItem(
        //this.GetFruits<IApple>().ProcessItem(
        //this.Apples.MakeAppleJuice(            
    }
}

public class Foo
{
    public void Main()
    {
        var context = new Context();
        Check(new IFruits<IFruit>[] { context.GetFruits<IApple>() });
    }
    public void Check(IEnumerable<IFruits<IFruit>> fruits) 
    {
        foreach (var fruit in fruits)
            fruit.Check();
    }
}

Upvotes: 1

Bozhidar Stoyneff
Bozhidar Stoyneff

Reputation: 3634

You need to construct a covariant interface in order to accomplish this. Have you heard of the variance in C# and variant interfaces in particular? You need to re-define IFruitsBase<T> to be covariant. I.e., use the out keyword on the generic parameter and constraint it to be an IFruit, like this IFruitsBase<out T> where T : IFruit

Start off by defining interfaces for the Fruit and Apple classes and implement them in your actual classes:

public interface IFruit
{
}

public interface IApple : IFruit
{
}

public class Fruit : IFruit
{
}

public class Fruit : IFruit
{

}

public class Apple : Fruit, IApple
{

}

Then go with the plurals (I hope this is just for the example, right?). IMHO you can get rid of the non-generic interface IFruits and rename the generic IFruitsBase<T> as IFruits<T>. As a coding standard, you apply the Base suffix to classes meant to be inherited.

public interface IFruits<out T> where T : IFruit
{
    T GetItem();
    void ProcessItem(IFruit fruit);
    void Check();

}

public interface IApples : IFruits<IApple>
{
    void MakeAppleJuice(IEnumerable<IApple> apples);
}

public class Fruits<T> : IFruits<T> where T : IFruit
{
    public void Check()
    {
        throw new NotImplementedException();
    }

    public T GetItem()
    {
        throw new NotImplementedException();
    }

    public void ProcessItem(IFruit fruit)
    {
        if (fruit is T)
        {

        }
        else
        {
            throw new NotSupportedException();
        }
    }
}

public class Apples : Fruits<IApple>, IApples
{
    public void MakeAppleJuice(IEnumerable<IApple> apples)
    {
        // Do the juice
    }
}

Then modify the Context class a bit:

public class Context
{
    public IApples Apples { get; set; }

    public IBananas Bananas { get; set; }

    public Context()
    {
        Apples = new Apples();
    }

    public IFruits<T> GetFruits<T>() where T : IFruit
    {
        return new Fruits<T>();
    }
}

Now the MakeAppleJuice() method is available ONLY when you access context.Apples.

Upvotes: 1

romain-aga
romain-aga

Reputation: 1561

Well, it doesn't do 100% of your wish (because it seems pretty impossible to accomplish), but it should do the trick.

public interface IFruits
{
    Fruit GetItem();
    void ProcessItem(Fruit fruit);
    void Check();
}

// I changed the name of your IFruitsBase, because it's the same thing as IFruits
// No need to have 2 differents names to name the same thing    
public interface IFruits<T> : IFruits where T : Fruit
{
    T GetItem();
    void ProcessItem(T fruit);
    void Check(); // This one could probably be removed from this interface
}

Then:

public interface IApples : IFruits<Apple>

And:

public class Fruits<T> : IFruits<T> where T : Fruit

Now, IFruits inherits from IFruits, so no more conflicts, because you won't implement twice the same interface.


So, if you do:

var context = new Context();
context.Apples.[You will have access to both IFruits and IFruits<Apple>]

And with this:

var context = new Context();
context.GetFruits(item.GetType()).[only IFruits methods are accessable]

Upvotes: 1

Taekahn
Taekahn

Reputation: 1692

I think if you unpack what you're trying to do here, a solution presents itself.

First, I dislike using plural classes to represent a list. i.e.

Apple
Apples : List<Apple>

That's not exactly what you were doing, but its basically the same thing. IMO doing this creates harder to maintain code and can obscure what you're trying to do. Instead I've replaced it with extension methods on arrays of Apple

I also pulled out everything I wasn't using, so I may have missed something, but I tried to include everything it seemed like you were asking for. This is what I would go with based off what you have.

public class Context
{
    public Apple[] Apples { get; set; }

    public IFruit[] GetFruits()
    {
        return null;
    }
}

public interface IFruit
{

}

public class Apple : IFruit
{

}

public class Banana : IFruit
{

}

public static class Apples
{
    public static void MakeAppleJuice(this Apple[] apples)
    {

    }

    public static void ProcessItem(this Apple[] apples, Apple fruit)
    {
    }
}



class Program
{
    static void Foo(IFruit[] fruits)
    {

    }

    static void Main(string[] args)
    {
        var context = new Context();
        context.Apples.MakeAppleJuice();
        context.GetFruits();
        Foo(context.Apples); //Dangerous call, but legal.
        Foo(context.GetFruits());
        context.Apples.ProcessItem(new Banana()); // Does not compile.
    }
}

Upvotes: 0

Patrick Hofman
Patrick Hofman

Reputation: 156978

In my opinion, your public interface IFruits : IFruitsBase<Fruit> is wrong. You don't declare a non-generic interface based on a generic one, you do it the other way around. That way it makes more sense.

This is what I have come up with:

public interface IFruitsBase<T> where T : Fruit
{
    T GetItem();
    void ProcessItem(T fruit);
}

public interface IFruits
{
    Fruit GetItem();
    void ProcessItem(Fruit fruit);

    void Check();
}

public class Fruits<T> : IFruitsBase<T>, IFruits where T : Fruit
{
    public T GetItem()
    {
        return null;
    }
    public void ProcessItem(T fruit)
    {

    }
    public void Check()
    {

    }

    Fruit IFruits.GetItem()
    {
        throw new NotImplementedException();
    }
    void IFruits.ProcessItem(Fruit fruit)
    {
        ProcessItem((T)fruit);
    }
}

// no changes from here on

public class Apples : Fruits<Apple>, IApples
{
    public void MakeAppleJuice(IEnumerable<Apple> apples)
    {

    }
}

public class Fruit
{

}

public class Apple : Fruit
{

}

public interface IApples : IFruitsBase<Apple>, IFruits
{
    void MakeAppleJuice(IEnumerable<Apple> apples);
}

Upvotes: 0

Related Questions