0

I have a set of interfaces using each others like this:

public interface IModel 
{
    string Name { get; }

    IModelParameters Parameters { get; }
}

public interface IModelParameter
{
    int Value { get; }
}

public interface IModelParameters: IList<IModelParameter>
{
    void DoSomething();
}

And to implement those interfaces, I have defined those classes:

public class Model: IModel 
{
    string Name { get; internal set; }

    public ModelParameters Parameters { get; private set; }

    IModelParameters IModel.Parameters { get { return Factors; } }
}

public class ModelParameter: IModelParameter
{
    int Value { get; internal set; }
}

public class ModelParameters: List<ModelParameter>, IModelParameters
{
    void DoSomething() 
    {
        // actual code
    }
}

This does not compile because List<ModelParameter> implements IList<ModelParameter> and not IList<IModelParameter> as required by IModelParameters

Changing ModelParameters to be List<IModelParameter> fixes the compilation but it breaks Entity Framework migration generation because it no longer recognizes the list as a navigation property because the type parameter is an interface, not a regular class.

I could also have ModelParameters not implement IModelParameters and declare a second class that gets instantiated and filled directly in the IModelParameters.Factors getter inside Model
But this feels inefficient as it effectively creates two instances of the same list, one for Entity framework and a temporary one for use by the rest of the application. And because this temporary is filled at runtime, it introduces another potential point of failure.

This is why I'm trying to find a way to express the fact List<ModelParameter> implements IList<IModelParameter> just fine because ModelParameter implements IModelParameter itself.

I have a feeling that covariance/contravariance might be of help here, but I'm not sure how to use that.

OBones
  • 310
  • 2
  • 13
  • 1
    Have a look into [Why not inherit from List?](https://stackoverflow.com/questions/21692193/why-not-inherit-from-listt) – Fildor May 11 '22 at 13:54

1 Answers1

0

You cannot do this. It it was possible to cast a List<ModelParameter> to IList<IModelParameter> you could try adding a object of another type to the list, i.e. class MyOtherModelParam : IModelParameter. And that is a contradiction since the type system guarantees that the list only contains ModelParameter objects.

You could replace it with IReadOnlyList<T>, since this interface do not expose any add or set methods it is safe to cast a List<ModelParameter> to IReadOnlyList<IModelParameter>.

Another possible solution would be to just remove the interface. If you intend to have only one implementation of IModelParameter, the interface serves little purpose, and you might as well just remove it.

JonasH
  • 28,608
  • 2
  • 10
  • 23
  • Yes, I agree with the type check, it makes sense. If I replace `IList` with `IReadOnlyList`, it still complains about two missing implementations but that's more acceptable than with `IList<>`. As to removing `IModelParameter` altogether, I'd rather avoid this as it feels weird to have an interface property return a list of regular objects. – OBones May 11 '22 at 14:15
  • @OBones there is no good reason only to return interfaces in a interface, it really depends on what the type represents. Consider for example the built in types like `TimeSpan` and `DateTime`. There is nothing at all wrong with returning these in an interface. – JonasH May 11 '22 at 14:20