9

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.
        }
    }
}
simendsjo
  • 4,739
  • 2
  • 25
  • 53
  • I am finding it hard to understand exactly what the question is. Are you asking why any particular piece of code does not work? If so, can you provide just that bit of code along with a brief description of why you believe it ought to behave differently? – Eric Lippert Nov 09 '10 at 23:35
  • @Eric: I've added a more complete example. Hope this clarifies a bit. – simendsjo Nov 10 '10 at 08:13

2 Answers2

2

If you want to insert and return the same type of object, you need invariance. You just have to declare your variable with that in mind.

Just use your first snippet with that line to declare the repo variable:

IRepo<MyModel> repo = new Repo();

Edit: I have taken the necessary 10 minutes to write the code necessary. I can assure you that that bit of code compiles on my computer (on Visual C# Express):

public interface IModel {

}

public interface IRepo<T> where T : IModel {
    T returnModel();
    void putModel(T model);
}

public class MyModel : IModel {

}

public class Repo : IRepo<MyModel> {
}

public static class Program {
    void main() {
        IRepo<MyModel> repo = new Repo();
        var model = new MyModel();
        repo.putModel(model);
        var model2 = repo.returnModel();
    }
}
Jean Hominal
  • 16,518
  • 5
  • 56
  • 90
  • You'll need to try my examples. It doesn't compile – simendsjo Nov 09 '10 at 15:32
  • Ah, sorry. Didn't notice you used MyModel instead of IModel. The whole reason for this _is_ the IModel. I want to store an IEnumerable> someplace, and I want to create those by using my service factory: ServiceFactory.CreateAll>... – simendsjo Nov 09 '10 at 15:42
  • Deleted my previous comment because I saw your other question. – Jean Hominal Nov 09 '10 at 15:58
2

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.

kvb
  • 54,864
  • 2
  • 91
  • 133
  • Very nice! I can even have an IRepo : IReadRepo, IWriteRepo and have SomeBase : IRepo and SomeImpl : SomeBase. Thank you so much! – simendsjo Nov 11 '10 at 08:15
  • And as a side note; I don't want on the writeable repo, just as it needs to work on just concrete classes – simendsjo Nov 11 '10 at 08:16