I think I'll explain my problems with some examples..
interface IModel {}
class MyModel : IModel {}
interface IRepo<T> where T: IModel {
}
class Repo : IRepo<MyModel> {
}
// Cannot implicitly convert.. An explicit convertion exists. Missing cast?
IRepo<IModel> repo = new Repo();
So I need covariance..
interface IRepo<out T> where T: IModel {
}
Nice, it works. Then I want to use it:
interface IRepo<out T> where T: IModel {
    T ReturnSomething();
}
class Repo : IRepo<MyModel> {
    public MyModel ReturnSomething() { return default(MyModel); }
}
All good, but the repo needs to insert objects too. With an out parameter, we cannot do this:
// Invalid variance: The type parameter 'T' must be contravariantly valid on 'IRepo<T>.InsertSomething(T)'. 'T' is covariant.
interface IRepo<out T> where T: IModel {
    T ReturnSomething();
    void InsertSomething(T thing);
}
class Repo : IRepo<MyModel> {
    public MyModel ReturnSomething() { return default(MyModel); }
    public void InsertSomething(MyModel thing) { }
}
So I try to add two parameters:
interface IRepo<out TReturn, TInsert>
    where TReturn : IModel
    where TInsert : IModel
{
    TReturn ReturnSomething();
    void InsertSomething(TInsert thing);
}
And I get the same error as in the very first example. I get the same error when using in TInsert
So how on earth could I support both insertion and fetching?
EDIT: So I've found a possible solution, but it's far from optimal
interface IRepo<out TResult> where TResult : IModel {
    TResult ReturnSomething();
    // I need to duplicate my constraint here..
    void InsertSomething<TInsert>(TInsert thing) where TInsert : IModel;
}
class Repo : IRepo<MyModel> {
    public MyModel ReturnSomething() { return default(MyModel); }
    // ... And here
    public void InsertSomething<T>(T thing) where T: IModel { }
}
EDIT2: In response to Eric: This is a more complete example of what I'm trying to achieve. I really would like covariance so I can group the IRepo instances, and I still want them to have add/update methods using the model as instance. I understand I cannot get compile-time type safety for adding items, but for this use case, I just need to read the elements.
interface IModel { }
class SomeModel : IModel { }
class OtherModel : IModel { }
interface IRepo<T>
{
    T ReturnSomething();
    void AddSomething(T thing);
}
interface ISubRepo<T> : IRepo<T> where T : IModel { }
class SomeSubRepo : ISubRepo<SomeModel> {
    public SomeModel ReturnSomething() { return default(SomeModel); }
    public void AddSomething(SomeModel thing) { }
}
class OtherSubRepo : ISubRepo<OtherModel> {
    public OtherModel ReturnSomething() { return default(OtherModel); }
    public void AddSomething(OtherModel thing) { }
}
class Program {
    static void Main(string[] args)
    {
        ISubRepo<IModel>[] everyone = new ISubRepo<IModel>[] {
            new SomeSubRepo(),
            new OtherSubRepo() 
        };
        WorkOnAll(everyone);
    }
    static void WorkOnAll(IEnumerable<ISubRepo<IModel>> everyone)
    {
        foreach(ISubRepo<IModel> repo in everyone) {
            IModel model = repo.ReturnSomething();
            // Etc.
        }
    }
}
I think that your best bet is to split your interface in two:
    interface IReadableRepo<out T> where T : IModel
    {
        T ReturnSomething();
    }
    interface IWritableRepo<in T> where T : IModel
    {
        void InsertSomething(T thing);
    }
    class Repo : IReadableRepo<MyModel>, IWritableRepo<MyModel>
    {
        ...
    }
Now you can create a List<IReadableRepo<IModel>> which contains Repo instances.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With