Reputation: 7440
I have the following setup
Base class
public class Fruit
{
}
Inherited class
public class Apple : Fruit
{
}
Generic Base interface
public interface IFruitsBase<T> where T : Fruit
{
T GetItem();
void ProcessItem(T fruit);
void Check();
}
Non generic Interface that inheritates from generic interface with the base class as the generic
public interface IFruits : IFruitsBase<Fruit>
{
}
Specific interface of the correspoing class
public interface IApples : IFruitsBase<Apple>, IFruits
{
void MakeAppleJuice(IEnumerable<Apple> apples);
}
Base implementation class
public class Fruits<T> : IFruitsBase<T>, IFruits where T : Fruit
{
public T GetItem()
{
return null;
}
public void ProcessItem(T fruit)
{
}
public void Check()
{
}
Fruit IFruitsBase<Fruit>.GetItem()
{
return this.GetItem();
}
void IFruitsBase<Fruit>.ProcessItem(Fruit fruit)
{
ProcessItem((T)fruit);
}
}
Specific implementation
public class Apples : Fruits<Apple>, IApples
{
public void MakeAppleJuice(IEnumerable<Apple> apples)
{
}
}
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
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
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
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
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
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