4

Given classes and interfaces below, I am wondering why implicit cast:

ISomeModelAbstract<IBasicModel> x = new ConcreteClass();

Is impossible. I tried

public interface ISomeModelAbstract<out T> where T: IBasicModel

But then I cannot use GetById and GetAll method. I appreciate any help or hint. Thank you.

public interface IBasicModel {
    string _id { get; set; }
}

public class SomeModel: IBasicModel {
    public string _id { get; set; }

    /* some other properties! */
}

public interface ISomeModelAbstract<T> where T: IBasicModel
{
    bool Save(T model);
    T GetById(string id);
    IEnumerable<T> GetAll();
    bool Update(string id, T model);
    bool Delete(string id);
}

public abstract class SomeModelAbstract<T> : ISomeModelAbstract<T> where T : IBasicModel
{
    public bool Save(T model)
    {
        throw new System.NotImplementedException();
    }

    public T GetById(string id)
    {
        throw new System.NotImplementedException();
    }

    public IEnumerable<T> GetAll()
    {
        throw new System.NotImplementedException();
    }

    public bool Update(string id, T model)
    {
        throw new System.NotImplementedException();
    }

    public bool Delete(string id)
    {
        throw new System.NotImplementedException();
    }
}

public interface IConcreteClass: ISomeModelAbstract<SomeModel> { }

public class ConcreteClass: SomeModelAbstract<SomeModel>, IConcreteClass { }
Rand Random
  • 7,300
  • 10
  • 40
  • 88
Node.JS
  • 1,042
  • 6
  • 44
  • 114
  • Thats because `ISomeModelAbstract` is not variant about `T`. If you make it `ISomeModelAbstract` then you can use the methods which return `T` but not those which take `T` as input parameter. It can either be *covariant* or *contravariant* but not both (which is what you are trying). – CodingYoshi May 07 '18 at 02:12
  • For that to work the interface has to be covariant, meaning the generic type only comes out but you have methods where it goes in as an argument. – juharr May 07 '18 at 02:12
  • Just because `SomeModel` is-a `IBasicModel` and that `SomeModelAbstract` is-a `ISomeModelAbstract` it **does not** mean that `SomeModelAbstract` is-a `ISomeModelAbstract`. – Enigmativity May 07 '18 at 02:30

2 Answers2

3

This doesn't work because of Covariance concern. Consider this sample code.

public class SomeModel2: IBasicModel {
    public string _id { get; set; }

    /* some other properties! */
}

After that, you can pass for example some object of SomeModel2 to Save method of x and obviously, this is not OK.

    ISomeModelAbstract<IBasicModel> x = new ConcreteClass();
    var m = new SomeModel2();
    x.Save(m);

To preventing this you should tell implicitly that you use your generic type only in return (out) places, not in inputs. For example:

public interface ISomeModelAbstract<out T> where T: IBasicModel

And after doing this, unfortunately, you can't use Save and Update method in your ISomeModelAbstract interface. Because they use T in parameter (input) place.

For more information please see link below: http://tomasp.net/blog/variance-explained.aspx/

AliJP
  • 668
  • 1
  • 4
  • 16
1

Another answer already describes the reason why it doesn't work in current state. I want to add that in such cases it's often useful to extract covariant or contravariant parts of your interface (or both) into separate interface(s). For example:

// covariant part, T is used only as return value
// ISomeModelRead is not the best name of course
public interface ISomeModelRead<out T> where T : IBasicModel {
    T GetById(string id);
    IEnumerable<T> GetAll();
}

// the rest of interface, also implementing covariant part
public interface ISomeModelAbstract<T> : ISomeModelRead<T> where T : IBasicModel  {
    bool Save(T model);
    bool Update(string id, T model);
    bool Delete(string id);
}

Now everything works the same, except you can do:

ISomeModelRead<IBasicModel> x = new ConcreteClass();
x.GetAll();
x.GetById("id");
Evk
  • 98,527
  • 8
  • 141
  • 191