-3

I'm trying to get a simple set of interfaces to be nested so I can enforce some members on derived classes.

 public enum Category { Mental, Physical, Technical }

        interface IAbilities
        {
            List<IAbility> Abilities { get; set; }
        }

        interface IAbility
        {
            Category Category { get; }
            int Value { get; set; }
            string Descritpion { get; set; }
        }

        public class MentalAbilities : IAbilities
        {
            // This is what I want so that "Abilities" is accessible
            public List<MentalAbility> Abilities { get; set; }

            // This is what Intellisense generates, cannot be set to public
            //List<IAbility> IAbilities.Abilities { get; set; }
        }

        public class MentalAbility : IAbility
        {
            public Category Category { get; } category
            public int Value { get; set; }
            public string Descritpion { get; set; }
        }

Of course the Intellisense generated bit compiles, but "Abilities" is not accessible from a class instance because it can't be set to public.

The bit I want tells me `'MentalAbilities' does not implement interface member 'IAbilities.Abilities'. 'MentalAbilities.Abilities' cannot implement 'IAbilities.Abilities' because it does not have the matching return type of List<IAbility>.

I don't understand since "MentalAbility" is derived from the "IAbility" interface so that should fulfill the contract.

Gab
  • 681
  • 4
  • 14
  • 27
  • 3
    Nothing is nested here. Nested means delcared in a class. You can declare enums, delegates, interfaces, classes in a class, so nested. But here, nothging is nested. And an interface can't be declared in an interface. –  Aug 15 '20 at 04:32
  • @OlivierRogier Ok I see. I thought nested meant interface in another interface. So my List is IAbilities is impossible to do? I've changed the interfaces to public and it does not solve my issue. – Gab Aug 15 '20 at 04:33

3 Answers3

3

(I'm skipping over the "x is less accessible than y" error because you already worked out you can make your interfaces public - sure you can appreciate how anything has to be at least as accessible as it's use)

Re your "does not implement" error:

I understand your problem, but you've kinda got it upside down

I see that you want to ensure, in your MentalAbilities.Abilities list, someone only puts MentalAbility objects.. and they implement IAbility so it should satisfy the "object is a IAbilities and has a list which contains only things that implement IAbility" rules, right?

Alas, no. This the pretty much the opposite of what inheritance or interface implementation is for.

The idea behind inheritance is that you can say "this thing can be any type at all as long as it has these common aspects and I will treat it in the common way". By declaring such that List<IAbility> you're saying "objects in this list just have to implement IAbility and then I'll be able to deal with them" - it could be a MentalAbility, or a PhysicalAbility - doesn't matter

You cannot then constrain the list to only containing MentalAbility via the inheritance/implements mechanism, nor should you because it's opposite of what it is for- going from "I can deal with anything so long as it implements X" to "I will only deal with something if it is, or is a subtype of, Y" is the opposite of what was claimed earlier. You're saying that MentalAbilities can only deal with the list contents if they're MentalAbility, when you earlier said it could deal with anything that was an IAbility

If you want to enforce that MentalAbilities only contains MentalAbility you'll have to declare it as List<IAbility> and look at the type of the IAbility you're given in the set and refuse to add it if if it's not a MentalAbility, but if other developers use your code they will be very confused - "what? It says I'm allowed to use anything that implements IAbility, and my TigerAbility does just that.. why am I getting an exception that it must be a MentalAbility? Why did this developer even use a List(IAbility) if I'm not allowed to sling any IAbility I like into it?" - it's also no longer a compile time thing, but a runtime one and that's a big risk

I think you need to decide if you're going to be able to treat these things in a generic way i.e. can your AbilitiesProcessor implement IAbilities and have a list of IAbility and process them regardless of actual type, or not. If it can't, and you're going to have a MentalAbilities class that can only deal with MentalAbility that's fine, and it can have a List<MentalAbility> all it wants, fellow developers won't mistakenly put a TigerAbility into it, but it won't implement the IAbilities interface

Caius Jard
  • 72,509
  • 5
  • 49
  • 80
  • Ok that makes it very clear, thank you. I'll find a better way to do this. I did not fully understood the uses of an interface. – Gab Aug 15 '20 at 05:00
1

I'm trying to get a simple set of interfaces to be nested so I can enforce some members on derived classes.

Your main issue here is that defined the two interfaces as private by default. That's what is preventing you from creating public List<IAbility> Abilities { get; set; } inside MentalAbilities.

Actually, the property public List<MentalAbility> Abilities { get; set; } is also preventing you as you can't have a property of the same name defined twice.

The big question here would be what would be the point of allowing MentalAbilities the ability to set a List<IAbility> since that would mean setting any type of ability.

Ideally MentalAbilities should only have a single list of MentalAbility that it contains - and it should only be read-only. In fact, most of your interfaces should be read-only.

Here's what you should do:

Start with these interfaces:

public interface IAbilities<A> where A : IAbility
{
    List<A> Abilities { get; }
}

public interface IAbility
{
    Category Category { get; }
    int Value { get; }
    string Description { get; }
}

Notice the IAbilities<A> interface uses a generic type A that you can fill in later.

Next, let's set up some base classes that simplify the implementation of each specific class.

public abstract class AbilitiesBase<A> : IAbilities<A> where A : IAbility
{
    public List<A> Abilities { get; protected set; }
}

public abstract class AbilityBase : IAbility
{
    public abstract Category Category { get; }
    public abstract int Value { get; }
    public abstract string Description { get; }
}

Now the final classes are simple:

public class MentalAbilities : AbilitiesBase<MentalAbility> { }

public class MentalAbility : AbilityBase
{
    public override Category Category => Category.Mental;
    public override int Value => 42;
    public override string Description => "Mental!";
}

There's nothing to implement in MentalAbilities - the base class took care of that - but it does have a List<MentalAbility> Abilities { get; } property.

MentalAbility is also neatly set up to enforce you to override the properties.

Now, if you actually wanted still have a List<IAbility> Abilities { get; } property then there's an easy way to do that by writing these two interfaces:

public interface IAbilities
{
    List<IAbility> Abilities { get; }
}

public interface IAbilities<A> : IAbilities where A : IAbility
{
    new List<A> Abilities { get; }
}

This forces a change to AbilitiesBase like this:

public abstract class AbilitiesBase<A> : IAbilities<A> where A : IAbility
{
    public List<A> Abilities { get; protected set; }
    List<IAbility> IAbilities.Abilities => this.Abilities.Cast<IAbility>().ToList();
}

The List<IAbility> IAbilities.Abilities property generates a copy of the public List<A> Abilities { get; protected set; } property, but that's what you want to prevent anyone adding the wrong type of ability to the original list.

Personally, I would go with this instead:

public interface IAbilities
{
    IEnumerable<IAbility> Abilities { get; }
}

public interface IAbilities<A> : IAbilities where A : IAbility
{
    new List<A> Abilities { get; }
}

public abstract class AbilitiesBase<A> : IAbilities<A> where A : IAbility
{
    public List<A> Abilities { get; protected set; }
    IEnumerable<IAbility> IAbilities.Abilities => this.Abilities.Cast<IAbility>();
}
Enigmativity
  • 113,464
  • 11
  • 89
  • 172
0

What you are stuck with is called implicit and explicit implementation of interfaces in C#. If you define your class member like this:

public List<MentalAbility> Abilities { get; set; }

you are implementing IAbilities implicitly and you need to write public with it. If you define your class member as

List<IAbility> IAbilities.Abilities { get; set; }

you have implemented IAbilities explicitly, in which case it can be private.

While we are here, I see no point in defining an interface IAbilities. You should simply use IEnumerable<IAbility> anywhere you want to use IAbilities, which is literally providing no value in your code.

marc_s
  • 732,580
  • 175
  • 1,330
  • 1,459
dotNET
  • 33,414
  • 24
  • 162
  • 251
  • Using `public List Abilities { get; set; }` generates the error mentionned below the post: `''MentalAbilities.Abilities' cannot implement 'IAbilities.Abilities' because it does not have the matching return type of List.` – Gab Aug 15 '20 at 04:41