0

Given public class BaseClass

that has derived classes, where a number of those follow the form

public class DerivedClass : BaseClass, ISpecificInterface

is there a way of specifying a collection that applies to just the derived classes that implement that interface?

For example, something like

public List<BaseClass where ISpecificInterface> myList; or

public List<BaseClass : ISpecificInterface> myList;

Paul Masri-Stone
  • 2,843
  • 3
  • 29
  • 51
  • public List myList; ? I suppose you are looking for something with more layers of inheritance, but maybe you should consider restructuring if you end up in this situation. – Palle Due Nov 24 '21 at 13:07
  • Not directly, but there are alternatives. What is your reasoning for wanting to constrain the list like this? – Johnathan Barclay Nov 24 '21 at 13:11
  • @JohnathanBarclay My use case is within Unity. If the list is visible in the Editor, then you can drag items of the list type into the list and Unity will constrain you to just the specified type. – Paul Masri-Stone Nov 24 '21 at 13:50
  • 1
    @PalleDue You said "maybe you should consider restructuring if you end up in this situation". I think I agree. Nonetheless it's good to understand this C# edge case. – Paul Masri-Stone Nov 24 '21 at 14:09

1 Answers1

2

You can only constrain generic parameters, not generic arguments. So you'll need:

public class DerivedClassWithInterfaceListContainer<TDerived>
    where TDerived : BaseClass, ISpecificInterface
{
    public List<TDerived> MyList { get; set; }
}

You may want to inherit List<T> for this instead:

public class DerivedList<TDerived> : List<TDerived>
    where TDerived : BaseClass, ISpecificInterface
{    
}

And then you can use it as property type:

public DerivedList<SomeDerivedClass> MyList { get; set; }

Point being: you can only declare the list as containing one type. So if you want a list that can hold any class derived from BaseClass and implementing ISpecificInterface, you must do so in a method:

// either ISpecificInterface _or_ BaseClass
private List<ISpecificInterface> myList;

public void AddToList<TDerived>(TDerived toAdd)
    where TDerived : BaseClass, ISpecificInterface
{
    myList.Add(toAdd);
}

You could then combine this:

public class DerivedList : List<ISpecificInterface>    
{    
    public new void Add<TDerived>(TDerived toAdd)
        where TDerived : BaseClass, ISpecificInterface
    {
        this.Add(toAdd);
    }
}

But now someone can cast your DerivedList to IList<ISpecificInterface> and call Add() on that, with an object implementing ISpecificInterface but not inheriting from BaseClass.

CodeCaster
  • 147,647
  • 23
  • 218
  • 272
  • What about `AddRange` and the indexer? What if a `BaseClass` member needs to be accessed on a list item? – Johnathan Barclay Nov 24 '21 at 13:29
  • It is fine (but indeed brittle) for private use if you know what you're doing and test that. It's not an ideal approach. The only solution I know of would be to have `DerivedList` implement IList or ICollection from scratch. – CodeCaster Nov 24 '21 at 13:32
  • @CodeCaster To check my understanding, when you say "generic parameters, not generic arguments", that means I couldn't have something list `public class List myList where TDerived : BaseClass, ISpecificInterface;`? – Paul Masri-Stone Nov 24 '21 at 13:53
  • @Paul yes, exactly. – CodeCaster Nov 24 '21 at 13:54