-1

I have a table controller that I'm using for several tables (Data Models). Here is a simplified version of it:

public abstract class TableBase
    {
        public virtual void TableName()
        {
            Console.WriteLine("I am Table: " + this.GetType().Name);
        }
    }

public class TableA : TableBase
    {
    }

public class TableB : TableBase
    {
    }

public class TableC : TableBase
    {
    }

public class Controller<T> where T : TableBase
    {
        public Controller(T table)
        {
            table.TableName();
        }

        public void Synchronize();
    }

Then I basically use it like this:

Controller<TableA> Controller1 = new Controller<TableA>(new TableA());
Controller<TableB> Controller2 = new Controller<TableB>(new TableB());
Controller<TableC> Controller3 = new Controller<TableC>(new TableC());

Everything is easy breezy but the problem comes when I want to add the controllers to a list of controllers:

List<Controller<TableBase>> ControllerList = new List<Controller<TableBase>>();
ControllerList.Add(Controller1);
ControllerList.Add(Controller2);
ControllerList.Add(Controller3);

It tells me that I can't convert Table(A,B,C) to type of TableBase, for some reason using the base as a type in the controller class freaks everything out. I wouldn't think this is causing a variance issue but it seems to be. All I'm wanting to do is call Synchronize() on each controller in a loop. How do i get this to work?

jdphenix
  • 15,022
  • 3
  • 41
  • 74
  • Let us [continue this discussion in chat](https://chat.stackoverflow.com/rooms/168343/discussion-between-jdphenix-and-beatnikthedan). – jdphenix Apr 05 '18 at 19:35
  • C# does not support covariance on class types, only on interfaces and delegates. – Eric Lippert Apr 05 '18 at 20:41
  • It should be clear why that is. Suppose you had `class Cage where T : Animal { public T Animal { get; set; } }` and you convert a `Cage` to `Cage`. Now you can call `cage.Animal = new Tiger();` and you have a tiger in your aquarium. – Eric Lippert Apr 05 '18 at 20:43
  • This question is a duplicate of [this question](https://stackoverflow.com/questions/2033912/c-sharp-variance-problem-assigning-listderived-as-listbase) which covers why this is happening and how to fix it. – Servy Apr 06 '18 at 13:27

1 Answers1

0

If you need to call a common method in otherwise incompatible types, you can define an interface that exposes the functionality you need to call.

In this code sample, I added a new interface ICanSync which has method signature matching Synchronize() from your Controller<T>, and modified Controller<T> to implement the new interface. This means you can create a List<ICanSync> and add controllers with incompatible generic types.

// new interface to use
public interface ICanSync
{
    void Synchronize();
}

public abstract class TableBase
{
    public virtual void TableName()
    {
        Console.WriteLine("I am Table: " + this.GetType().Name);
    }
}

public class TableA : TableBase
{
}

public class TableB : TableBase
{
}

public class TableC : TableBase
{
}

public class Controller<T> : ICanSync where T : TableBase
{

    private TableBase _t;

    public Controller(T table)
    {
        _t = table;
    }

    public void Synchronize()
    {
        _t.TableName();
    }
}

You can declare a List<ICanSync> and call Synchronize() on all of them.

Controller<TableA> Controller1 = new Controller<TableA>(new TableA());
Controller<TableB> Controller2 = new Controller<TableB>(new TableB());
Controller<TableC> Controller3 = new Controller<TableC>(new TableC());

List<ICanSync> ControllerList = new List<ICanSync> {Controller1, Controller2, Controller3};

foreach (var controller in ControllerList) 
{
    controller.Synchronize();
}

This is type safe, because ICanSync is naturally compatible with instances of itself. If you need richer common functionality that uses T, you can declare a covariant or contravariant interface.

jdphenix
  • 15,022
  • 3
  • 41
  • 74
  • Why in the world would you vote to reopen a question only to post an answer that uses the exact same solution as the duplicate, but with just a code dump with no explanation, inhibiting the ability of any reader to actually understand what's going on? – Servy Apr 06 '18 at 13:27
  • There was an extended discussion in chat which is relevant. If you believe my answer is poor, feel free to downvote it. I'll add additional information as to why this solves the question's problem. – jdphenix Apr 06 '18 at 15:17
  • the solution for this was different than what was posted as duplicate. The real problem wasn't using an Interface, I was already doing that. I found the real problem was I was using an Interface that was passing a type which the code sees each type as a different interface so it wasn't working. I fixed it by making a second interface that isn't passing a type to do the syncing method that I needed to do. So not that same problem at as the infamous "duplicate question" – beatnikthedan May 24 '18 at 19:16