Question

I am trying to learn C# and have up until this point worked a lot with Java.

Now I have several times already met this problem and it is starting to annoy me a bit so for the sake of example lets look at some code.

Say for instance I have the following abstract class:

public abstract class ChartHelper
{
    public System.Windows.Forms.DataVisualization.Charting.Chart resultChart { get; set; }
    public String TimeType { get; set; }
    protected  List<IObject> _datalist;
    protected  TimeType _timeType;
    protected  DateTime _stopDate;
    protected  DateTime _startDate;

}

Now as you can see i have a List<> that will contain elements of the interface IObject:

    public interface IObject
{
    DateTime GetPeriod();

}

Now an element that implements this interface could be the following:

    public class Email : IObject
{
    public virtual DateTime PERIOD { get; set; }
    public virtual int email_u_kam { get; set; }
    public virtual int kam_ialt { get; set; }
    public virtual int ms_besvarelses_pc { get; set; }
    public virtual int total_besvarelses_pc { get; set; }
    public DateTime GetPeriod()
    {
        return PERIOD;
    }
}

Now for an extension of the ChartHelper class I would like to create an Email chart constructor and for this purpose I of course extend the ChartHelper class:

        public class EmailChartGenerator : ChartHelper, IChart
    {

        public EmailChartGenerator(List<Email> dataList, TimeType timeType, DateTime startDate, DateTime stopDate) : base()
        {
            _startDate = startDate;
            _stopDate = stopDate;
            _datalist = dataList;
            _timeType = timeType;
        }
}

Notice that in the constructor I specify the list as the type Email since this generator will only be used for creating charts that display Email data.

And now to the problem. because the protected field of the abstract class ChartHelper _datalist is specified as IObject I get the following error in my generator's constructor:

   Cannot implicitly convert type 'System.Collections.Generic.List<Henvendelser.Objects.Email>' to 'System.Collections.Generic.List<Henvendelser.Objects.IObject>'

even though the Email object is implementing the interface IObject. I know this was kind of a long post but I needed to make sure I had all the details! Why is this happening and shouldn't Email be equal to IObject?

Was it helpful?

Solution

What you are facing is related into co- and contravariance. In general this means that generic type parameters can only implicitly converted into certain generalizations or specializations under special circumstances.

You can challange this problem from two sides: explicitly convert your types at runtime or simplify your design.

The first way is easier to apply, but since you are solving the problem at runtime, it may not be the best solution in your case.

public EmailChartGenerator(List<Email> dataList, TimeType timeType, DateTime startDate, DateTime stopDate) : base()
{
    _startDate = startDate;
    _stopDate = stopDate;
    _datalist = dataList.ConvertAll(e => (IObject)e);
    _timeType = timeType;
}

For more information read on here.

The second way is to determinate the nature of your base collection. If you only want to collect some data within a collection and initialize it from an random collection, use IEnumerable:

public abstract class ChartHelper
{
    protected  IEnumerable<IObject> _datalist;

    public ChartHelper()
    {
        _datalist = new List<IObject>();
    }

    public ChartHelper(IEnumerable<IObject> data)
    {
        _datalist = data;
    }
}

Since IEnumerable is covariant, you can implement your constructor like this:

public EmailChartGenerator(List<Email> dataList, TimeType timeType, DateTime startDate, DateTime stopDate) 
    : base(dataList)
{
    _startDate = startDate;
    _stopDate = stopDate;
    _timeType = timeType;
}

However you can also easily maintain a list within your base class and initialize it from your collection:

public abstract class ChartHelper
{
    protected  List<IObject> _datalist;

    public ChartHelper()
    {
        _datalist = new List<IObject>();
    }

    public ChartHelper(IEnumerable<IObject> data)
    {
        _datalist = new List<IObject>(data);
    }
}

Sure you can further improve this design, but in general it should do exactly what you want.

OTHER TIPS

No, Email shouldn't be equal to IObject. Therefore, a List<Email> is not a List<IObject> because List<T> has an Add(T) method and thus the collection can't be covariant. For example, an originally List<Email>, when converted to List<IObject>would cause its Add(T) method to become Add(IObject) and allow any IObject in, not necessarily Email. Thus, you could get a completely unrelated object out of (what looks like) a List<Email>.

On the other hand, an IEnumerable<Email> can be assigned to IEnumerable<IObject>, because IEnumerable<T> offers a read-only interface and thus can be (and is) covariant.

If you want to convert between List<A> and List<B> (and there's a legal conversion between the two), use the Cast<T>() method.

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top