18

I'm having an interface IJob from which all Job classes inherits and I have generic repo class which will process all kind of IJob's.

The problem im facing is im not able to able convert Repository<Job1> to Repository<IJob> even though Job1 is a type of IJob.

Code

internal class Program
{
    public interface IJob { }
    public class Job1 : IJob { }
    public class Job2 : IJob { }

    public class Repository<TJob>
        where TJob : IJob
    {
        public List<TJob> GetJobs()
        {
            return new List<TJob>();
        }
    }

    private static void Main(string[] args)
    {
        IJob iJob = new Job1(); // Valid
        Repository<IJob> repo = new Repository<Job1>(); // Not Valid
    }
}

Can someone let me know why this wont be possible in C# and are there any other rules type conversion in C# generics?

Umamaheswaran
  • 3,690
  • 3
  • 29
  • 56
  • 13
    You're looking for generic variance... but in this case you won't be able to make your class covariant, as classes don't support variance. See https://msdn.microsoft.com/en-us/library/ms228359(v=vs.90).aspx – Jon Skeet Mar 16 '16 at 14:22
  • @JonSkeet Sure, Let me have a look – Umamaheswaran Mar 16 '16 at 14:25
  • You'll have to do something like have Repository explicitly implement a non-generic IRepository interface which provides the same type of access to a collection of IJob as Repository does to a collection of a concrete Job class. – 15ee8f99-57ff-4f92-890c-b56153 Mar 16 '16 at 14:27
  • 13
    This question is asked every day here. One more time: a box of oranges is not a box of fruit. Why not? Because you can put an apple into a box of fruit, but not into a box of oranges. A box of fruit is not a box of oranges. Why? Because a box of fruit might contain an apple. Conclusion: there is no type relationship between a box of fruit and a box of oranges, regardless of whether there is a relationship between fruit and orange. – Eric Lippert Mar 16 '16 at 18:34

4 Answers4

18

If you want covariance, then you'll have to introduce a generic interface for the repository and change the return type of the GetJobs method to IEnumerable<TJob>:

internal class Program
{
    public interface IJob { }
    public class Job1 : IJob { }
    public class Job2 : IJob { }

    public interface IRepository<out TJob> where TJob : IJob
    {
        IEnumerable<TJob> GetJobs();
    }

    public class Repository<TJob> : IRepository<TJob> where TJob : IJob
    {
        public IEnumerable<TJob> GetJobs()
        {
            return new List<TJob>();
        }
    }

    private static void Main(string[] args)
    {
        IJob iJob = new Job1(); // Valid
        IRepository<IJob> repo = new Repository<Job1>(); // Also valid
    }
}

For more information about covariance and contravariance, see: https://msdn.microsoft.com/en-us/library/dd799517%28v=vs.110%29.aspx

11
Repository<IJob> repo = new Repository<Job1>(); // Not Valid

Yes, because it isn't an Repository<IJob>. With an Repository<IJob>, this line would be valid:

repo.GetJobs().Add(new Job2());

Now, with a Repository<Job1>, this line is invalid. It won't compile.

So the error you get is correct. A Repository<Job1> is not a Repository<IJob>.

nvoigt
  • 75,013
  • 26
  • 93
  • 142
2

You simply want

Repository<IJob> repo = new Repository<IJob>();

then you can add any IJob to the repository.

repo.Add(new Job1());
repo.Add(new Job2());
Kirk Broadhurst
  • 27,836
  • 16
  • 104
  • 169
1

to understand why it cannot be done we need to go back what means to be a subclass of an object in c#. when you have a situation like this:

public class A 
{
    public A GetInstance { get;set;}
}

public class B : A
{

}

class B inherits all methods from class A including the signature of the method. in fact, you can create an instance of B that has a GetInstance method ThatReturns A, but not B. if you try to override the GetInstance method to return B, it will of course give a compilation error (a method cannot be ovveridden if the only change is the return type).

using a generics is a bit different because changing the type in input will change the signature of all the methods.

Simply put, even if Job1 is a subclass of IJob, methods of class Repository(IJob) have a different signature of methods of class Repository(Job1) so they are not related, but like two separate classes.