Ian
Ian

Reputation: 34489

How to use Inheritance when using Generic Constraints

I'm struggling with some Generic constraint issues when trying to implement a library that allows inheritance and hoping someone can help.

I'm trying to build up a class library that has 3 flavours to it, each building on top of the other. To me it seemed like a perfect opportunity to use Generics as I can't quite do what I want through pure inheritance. The code's below (this should paste straight into VS) with some explanation afterwards:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace Test
{
    #region Base Classes

    public class GenericElement { }

    /// <summary>Visit to a GenericElement</summary>
    public class Generic_Visit<E> where E : GenericElement
    {
        public E Element { get; set; }
    }

    /// <summary>Collection of Visits</summary>
    public class Generic_Route<V, E>
        where V : Generic_Visit<E>
        where E : GenericElement
    {
        public List<V> Visits { get; set; }
        public Double Distance { get; set; }
    }

    /// <summary>Collection of Routes</summary>
    public class Generic_Solution<R, V, E>
        where R : Generic_Route<V, E>
        where V : Generic_Visit<E>
        where E : GenericElement
    {
        public List<R> Routes { get; set; }

        public Double Distance
        {
            get
            {
                return this.Routes.Select(r => r.Distance).Sum();
            }
        }
    }

    #endregion

    #region TSP Classes

    public class Concrete_TSPNode : GenericElement { }

    public abstract class Generic_TSPVisit<E> : Generic_Visit<E>
        where E : Concrete_TSPNode
    {
        public Double Time { get; set; }
    }

    public abstract class Generic_TSPRoute<V, E> : Generic_Route<V, E>
        where V : Concrete_TSPVisit
        where E : Concrete_TSPNode
    {
        public Double Time
        {
            get
            {
                return this.Visits.Select(v => v.Time).Sum();
            }
        }
    }

    public abstract class Generic_TSPSolution<R, V, E> : Generic_Solution<R, V, E>
        where R : Concrete_TSPRoute
        where V : Concrete_TSPVisit
        where E : Concrete_TSPNode
    {
        public Double Time
        {
            get
            {
                return this.Routes.Select(r => r.Time).Sum();
            }
        }
    }

    public class Concrete_TSPVisit : Generic_TSPVisit<Concrete_TSPNode> { }

    public class Concrete_TSPRoute : Generic_TSPRoute<Concrete_TSPVisit, Concrete_TSPNode> { }

    public class Concrete_TSPSolution : Generic_TSPSolution<Concrete_TSPRoute, Concrete_TSPVisit, Concrete_TSPNode> { }

    #endregion

    #region VRP

    public class Concrete_VRPNode : Concrete_TSPNode { }

    public abstract class Generic_VRPVisit<E> : Generic_TSPVisit<E> where E : Concrete_VRPNode
    {
        public Double Capacity { get; set; }
    }

    public abstract class Generic_VRPRoute<V, E> : Generic_TSPRoute<V, E>
        where V : Concrete_VRPVisit
        where E : Concrete_VRPNode
    {
        public Double Capacity
        {
            get
            {
                return this.Visits.Select(v => v.Capacity).Sum();
            }
        }
    }

    public abstract class G_VRPSolution<R, V, E> : Generic_TSPSolution<R, V, E>
        where R : Concrete_VRPRoute
        where V : Concrete_VRPVisit
        where E : Concrete_VRPNode
    {
        public Double Capacity
        {
            get
            {
                return this.Routes.Select(r => r.Capacity).Sum();
            }
        }
    }

    public class Concrete_VRPVisit : Generic_VRPVisit<Concrete_VRPNode> { }

    public class Concrete_VRPRoute : Generic_VRPRoute<Concrete_VRPVisit, Concrete_VRPNode> { }

    public class Concrete_VRPSolution : Generic_TSPSolution<Concrete_VRPRoute, Concrete_VRPVisit, Concrete_VRPNode> { }

    #endregion
}

The idea behind the code is that there are a set of base classes that expose properties using Generics. This allows me to have strongly typed collections for example.

There are then 2 of the 3 stages build upon these, TSP and VRP in the example which have 4 concrete classes (these are what the developer using the class library should be interacting with as the generic constraints just get a bit crazy) - Element, Visit, Route and Solution.

There are also some classes prefixed Generic for both TSP and VRP. These allow the inheritance that I want as it exposes the Generic Types. If I don't use these (say Concrete_VRPRoute inherits Concrete_TSPRoute) then I have to keep casting the type of item returned by the Visits collection to get the Capacity property for example.

I'm fairly confident all the types line up correctly, but when I try to build I get the following errors and I really don't know how to tackle them.

Error 1 The type 'V' cannot be used as type parameter 'V' in the generic type or method 'Test.Generic_Route'. There is no implicit reference conversion from 'V' to 'Test.Generic_Visit'.

Error 2 The type 'V' cannot be used as type parameter 'V' in the generic type or method 'Test.Generic_Solution'. There is no implicit reference conversion from 'V' to 'Test.Generic_Visit'.

Error 3 The type 'R' cannot be used as type parameter 'R' in the generic type or method 'Test.Generic_Solution'. There is no implicit reference conversion from 'R' to 'Test.Generic_Route'

Error 4 The type 'V' cannot be used as type parameter 'V' in the generic type or method 'Test.Generic_TSPRoute'. There is no implicit reference conversion from 'V' to 'Test.Concrete_TSPVisit'.

Error 5 The type 'V' cannot be used as type parameter 'V' in the generic type or method 'Test.Generic_TSPSolution'. There is no implicit reference conversion from 'V' to 'Test.Concrete_TSPVisit'.

Error 6 The type 'R' cannot be used as type parameter 'R' in the generic type or method 'Test.Generic_TSPSolution'. There is no implicit reference conversion from 'R' to 'Test.Concrete_TSPRoute'.

Error 7 The type 'Test.Concrete_VRPVisit' cannot be used as type parameter 'V' in the generic type or method 'Test.Generic_TSPSolution'. There is no implicit reference conversion from 'Test.Concrete_VRPVisit' to 'Test.Concrete_TSPVisit'.

Error 8 The type 'Test.Concrete_VRPRoute' cannot be used as type parameter 'R' in the generic type or method 'Test.Generic_TSPSolution'. There is no implicit reference conversion from 'Test.Concrete_VRPRoute' to 'Test.Concrete_TSPRoute'.'Test.Concrete_TSPRoute'.

Upvotes: 7

Views: 1021

Answers (3)

Timwi
Timwi

Reputation: 66573

Here is the same thing as Enigmativity’s answer, but with all the redundant constraints removed. This still compiles, it doesn’t have any generic recursion in it, and as far as I can tell is still as type-safe as was required. Enigmativity, what am I missing? :)

public class Generic_Element { }

public class Generic_Visit<E>
{
    public E Element { get; set; }
}

/// <summary>Collection of Visits</summary>
public class Generic_Route<V>
{
    public List<V> Visits { get; set; }
    public Double Distance { get; set; }
}

/// <summary>Collection of Routes</summary>
public class Generic_Solution<R, V>
    where R : Generic_Route<V>
{
    public List<R> Routes { get; set; }

    public Double Distance
    {
        get
        {
            return this.Routes.Select(r => r.Distance).Sum();
        }
    }
}

public class Generic_Tsp_Element : Generic_Element
{
}

/// <summary>Visit to a Generic_Element</summary>
public class Generic_Tsp_Visit<E> : Generic_Visit<E>
{
    public Double Time { get; set; }
}

/// <summary>Collection of Visits</summary>
public class Generic_Tsp_Route<V, E> : Generic_Route<V>
    where V : Generic_Tsp_Visit<E>
{
    public Double Time
    {
        get
        {
            return this.Visits.Select(v => v.Time).Sum();
        }
    }
}

/// <summary>Collection of Routes</summary>
public class Generic_Tsp_Solution<R, V, E> : Generic_Solution<R, V>
    where R : Generic_Tsp_Route<V, E>
    where V : Generic_Tsp_Visit<E>
{
    public Double Time
    {
        get
        {
            return this.Routes.Select(r => r.Time).Sum();
        }
    }
}

public class Concrete_Tsp_Element : Generic_Tsp_Element { }

public class Concrete_Tsp_Visit : Generic_Tsp_Visit<Concrete_Tsp_Element> { }

public class Concrete_Tsp_Route : Generic_Tsp_Route<Concrete_Tsp_Visit, Concrete_Tsp_Element> { }

public class Concrete_Tsp_Solution : Generic_Tsp_Solution<Concrete_Tsp_Route, Concrete_Tsp_Visit, Concrete_Tsp_Element> { }

public class Generic_Vrp_Element : Generic_Element
{
}

/// <summary>Visit to a Generic_Element</summary>
public class Generic_Vrp_Visit<V, E> : Generic_Visit<E>
{
    public Double Capacity { get; set; }
}

/// <summary>Collection of Visits</summary>
public class Generic_Vrp_Route<R, V, E> : Generic_Route<V>
    where V : Generic_Vrp_Visit<V, E>
{
    public Double Capacity
    {
        get
        {
            return this.Visits.Select(v => v.Capacity).Sum();
        }
    }
}

/// <summary>Collection of Routes</summary>
public class Generic_Vrp_Solution<S, R, V, E> : Generic_Solution<R, V>
    where R : Generic_Vrp_Route<R, V, E>
    where V : Generic_Vrp_Visit<V, E>
{
    public Double Capacity
    {
        get
        {
            return this.Routes.Select(r => r.Capacity).Sum();
        }
    }
}

public class Concrete_Vrp_Element : Generic_Vrp_Element { }

public class Concrete_Vrp_Visit : Generic_Vrp_Visit<Concrete_Vrp_Visit, Concrete_Vrp_Element> { }

public class Concrete_Vrp_Route : Generic_Vrp_Route<Concrete_Vrp_Route, Concrete_Vrp_Visit, Concrete_Vrp_Element> { }

public class Concrete_Vrp_Solution : Generic_Vrp_Solution<Concrete_Vrp_Solution, Concrete_Vrp_Route, Concrete_Vrp_Visit, Concrete_Vrp_Element> { }

Upvotes: 0

Timwi
Timwi

Reputation: 66573

OK, let’s examine the first one. The error is:

The type 'V' cannot be used as type parameter 'V' in the generic type or method 'Test.Generic_Route'. There is no implicit reference conversion from 'V' to 'Test.Generic_Visit'.

It complains about this declaration:

public abstract class Generic_TSPRoute<V, E> : Generic_Route<V, E>
    where V : Concrete_TSPVisit
    where E : Concrete_TSPNode

This establishes two definitions:

  • V is a Concrete_TSPVisit (or a descendent of it)

  • E is a Concrete_TSPNode (or a descendent of it)

Now let’s see what Generic_Route<V, E> lets us put in:

public class Generic_Route<V, E>
    where V : Generic_Visit<E>
    where E : GenericElement

The second constraint is fine because Concrete_TSPNode is a GenericElement. The first one is problematic: Remember that E is a Concrete_TSPNode or a descendent of it, therefore Generic_Visit<E> could be:

  • Generic_Visit<Concrete_TSPNode>, or

  • Generic_Visit<some subclass of Concrete_TSPNode>

However, we also know from earlier that V is a Concrete_TSPVisit (or a descendent of it).

  • Concrete_TSPVisit inherits from Generic_TSPVisit<Concrete_TSPNode>

  • Generic_TSPVisit<Concrete_TSPNode> inherits from Generic_Visit<Concrete_TSPNode>

Notice something? This requires it to be a Generic_Visit<Concrete_TSPNode>. It is emphatically not allowed to be Generic_Visit<some subclass of Concrete_TSPNode>.

In other words, imagine I write this:

var route = new Generic_TSPRoute<Concrete_TSPVisit, Concrete_TSPNode_Subclass>();

According to your hierarchy, Concrete_TSPVisit is a Generic_Visit<Concrete_TSPNode> and therefore has a property that looks like

public Concrete_TSPNode Element { get; set; }

If I retrieve a value from this property, it is only guaranteed to be a Concrete_TSPNode but not necessarily a Concrete_TSPNode_Subclass.

EDIT:

I’ll leave this answer because it explains the reason for the compiler error, but Enigmativity’s answer actually provides the solution to the problem.

Upvotes: 2

Enigmativity
Enigmativity

Reputation: 117064

It's a piece of generic cake. You need to define the generic classes in terms of themselves. A recursive generic definition.

Base Classes:

public class Generic_Element<E>
    where E : Generic_Element<E>
{
}

/// <summary>Visit to a Generic_Element</summary>
public class Generic_Visit<V, E>
    where V : Generic_Visit<V, E>
    where E : Generic_Element<E>
{
    public E Element { get; set; }
}

/// <summary>Collection of Visits</summary>
public class Generic_Route<R, V, E>
    where R : Generic_Route<R, V, E>
    where V : Generic_Visit<V, E>
    where E : Generic_Element<E>
{
    public List<V> Visits { get; set; }
    public Double Distance { get; set; }
}

/// <summary>Collection of Routes</summary>
public class Generic_Solution<S, R, V, E>
    where S : Generic_Solution<S, R, V, E>
    where R : Generic_Route<R, V, E>
    where V : Generic_Visit<V, E>
    where E : Generic_Element<E>
{
    public List<R> Routes { get; set; }

    public Double Distance
    {
        get
        {
            return this.Routes.Select(r => r.Distance).Sum();
        }
    }
}

TSP Classes:

public class Generic_Tsp_Element<E> : Generic_Element<E>
where E : Generic_Tsp_Element<E>
{
}

/// <summary>Visit to a Generic_Element</summary>
public class Generic_Tsp_Visit<V, E> : Generic_Visit<V, E>
    where V : Generic_Tsp_Visit<V, E>
    where E : Generic_Tsp_Element<E>
{
    public Double Time { get; set; }
}

/// <summary>Collection of Visits</summary>
public class Generic_Tsp_Route<R, V, E> : Generic_Route<R, V, E>
    where R : Generic_Tsp_Route<R, V, E>
    where V : Generic_Tsp_Visit<V, E>
    where E : Generic_Tsp_Element<E>
{
    public Double Time
    {
        get
        {
            return this.Visits.Select(v => v.Time).Sum();
        }
    }
}

/// <summary>Collection of Routes</summary>
public class Generic_Tsp_Solution<S, R, V, E> : Generic_Solution<S, R, V, E>
    where S : Generic_Tsp_Solution<S, R, V, E>
    where R : Generic_Tsp_Route<R, V, E>
    where V : Generic_Tsp_Visit<V, E>
    where E : Generic_Tsp_Element<E>
{
    public Double Time
    {
        get
        {
            return this.Routes.Select(r => r.Time).Sum();
        }
    }
}

public class Concrete_Tsp_Element : Generic_Tsp_Element<Concrete_Tsp_Element> { }

public class Concrete_Tsp_Visit : Generic_Tsp_Visit<Concrete_Tsp_Visit, Concrete_Tsp_Element> { }

public class Concrete_Tsp_Route : Generic_Tsp_Route<Concrete_Tsp_Route, Concrete_Tsp_Visit, Concrete_Tsp_Element> { }

public class Concrete_Tsp_Solution : Generic_Tsp_Solution<Concrete_Tsp_Solution, Concrete_Tsp_Route, Concrete_Tsp_Visit, Concrete_Tsp_Element> { }

VRP Classes:

public class Generic_Vrp_Element<E> : Generic_Element<E>
where E : Generic_Vrp_Element<E>
{
}

/// <summary>Visit to a Generic_Element</summary>
public class Generic_Vrp_Visit<V, E> : Generic_Visit<V, E>
    where V : Generic_Vrp_Visit<V, E>
    where E : Generic_Vrp_Element<E>
{
    public Double Capacity { get; set; }
}

/// <summary>Collection of Visits</summary>
public class Generic_Vrp_Route<R, V, E> : Generic_Route<R, V, E>
    where R : Generic_Vrp_Route<R, V, E>
    where V : Generic_Vrp_Visit<V, E>
    where E : Generic_Vrp_Element<E>
{
    public Double Capacity
    {
        get
        {
            return this.Visits.Select(v => v.Capacity).Sum();
        }
    }
}

/// <summary>Collection of Routes</summary>
public class Generic_Vrp_Solution<S, R, V, E> : Generic_Solution<S, R, V, E>
    where S : Generic_Vrp_Solution<S, R, V, E>
    where R : Generic_Vrp_Route<R, V, E>
    where V : Generic_Vrp_Visit<V, E>
    where E : Generic_Vrp_Element<E>
{
    public Double Capacity
    {
        get
        {
            return this.Routes.Select(r => r.Capacity).Sum();
        }
    }
}

public class Concrete_Vrp_Element : Generic_Vrp_Element<Concrete_Vrp_Element> { }

public class Concrete_Vrp_Visit : Generic_Vrp_Visit<Concrete_Vrp_Visit, Concrete_Vrp_Element> { }

public class Concrete_Vrp_Route : Generic_Vrp_Route<Concrete_Vrp_Route, Concrete_Vrp_Visit, Concrete_Vrp_Element> { }

public class Concrete_Vrp_Solution : Generic_Vrp_Solution<Concrete_Vrp_Solution, Concrete_Vrp_Route, Concrete_Vrp_Visit, Concrete_Vrp_Element> { }

The final result is non-generic concrete classes that can be used like this:

var e = new Concrete_Tsp_Element();
var v = new Concrete_Tsp_Visit();
v.Element = e;
v.Time = 0.5;
var r = new Concrete_Tsp_Route();
r.Visits = new List<Concrete_Tsp_Visit>(new[] { v });
r.Distance = 2.1;
var s = new Concrete_Tsp_Solution();
s.Routes = new List<Concrete_Tsp_Route>(new[] { r });
Console.WriteLine(s.Distance);
Console.WriteLine(s.Time);
Console.ReadLine();

Enjoy! Enjoy!

Upvotes: 6

Related Questions