1

I'm trying to provide a base class for a UI API to provide a Tab control. It's in C#.NET (4.5) but not using WinForms.

I have the tab control working perfectly, but a requirement has come up to have various customised tabs and pages, both in appearance and functionality, and rather than clone large chunks of code for each, I'm trying to provide a generic base class that you give a TTab and TPage type to.

The idea is in the API I can then provide classes which simply wrap the generic base into a non-generic class to actually use: DefaultTabs : BaseTabs<DefaultTab, DefaultTabPage>

I wasn't expecting this to be particularly troublesome, but I've managed to get all confused along the way, and I'm pretty sure I'm starting to go codeblind to the problem.

Here's a boiled down version:

namespace ExtendTabs.soExample
{
    using System.Collections.Generic;

    public class Example<TTab, TPage>
        where TTab : Tab<TPage>, new()
        where TPage : TabPage<TTab>, new()
    {
        private List<TTab> tabs;

        public Example()
        {
            this.tabs = new List<TTab>();

            this.tabs.Add(new TTab());
        }
    }

    public class Tab<TPage>
        where TPage : new()  //  where TPage : TabPage<..? All I can think of is "TabPage<Tab<TPage>>" and that seems very wrong.
    {
        public Tab()
        {
            this.Page = new TPage(); // Can't pass this in because of the where new() constraint.
            //this.Page.Tab = this;  // Can't do this because TPage does not contain where
            //this.Page.Link(this);  // Can't do this because TPage does not contain where
        }

        public TPage Page { get; private set; }
    }

    public class TabPage<TTab>
    {
        public TabPage() // Can't take in Tab<TPage> here because of type problems and new() constrain used.
        {
        }

        public TTab Tab { get; internal set; }

        internal void Link(TTab tab)
        {
            this.Tab = tab;
        }
    }
}

The 2 main problems are:

  • It doesn't compile on the main class line, with 'TPage' must be a non-abstract type with a public parameterless constructor in order to use it as parameter 'TPage' in the generic type or method 'ExtendTabs.soExample.Tab<TPage>'. I don't understand this error as TPage does have a public paramterless constructor and is non-abstract. (Thanks Siva)

  • I can't seem to add the TTab instance to the TPage instance without ending up with some sort of recursive generic type hell.

I'm hoping I've just been looking at this for too long and am trying to make this problem far too complicated. It seems like it should be a lot easier than this really. Any suggestions?


The first problem stemmed from the main class not including the same generic constraint as the subclass. As per the link Siva provided, I've now added the new() constraint and fixed that error.

Octopoid
  • 3,507
  • 5
  • 22
  • 43
  • 1
    [This](http://stackoverflow.com/questions/3056863/class-mapping-error-t-must-be-a-non-abstract-type-with-a-public-parameterless) should help you. – Siva Gopal Jan 13 '15 at 13:23
  • So, the hierarchy is a tab control will have tab which in turn can have tab pages? Is that correct? – danish Jan 13 '15 at 13:33
  • That's right, yeah - ideally I'd like to have a method of getting an enumeration of the pages up to the main tab control, but once the linkages work I'm sure I can sort that with a custom iterator. – Octopoid Jan 13 '15 at 13:36
  • The main thing is I need to be able to get at the TPage from the TTab, and the TTab from the TPage, because of the various different things it's used for. Ideally I'd like to be able to get at the tab container from both as well, but one thing at a time! – Octopoid Jan 13 '15 at 13:37

2 Answers2

1

I think I've solved this now:

namespace WindowsFormsApplication1.soExample
{
    using System.Collections.Generic;

    public abstract class BaseTabs<TTab, TPage>
        where TTab : BaseTabsTab<TTab, TPage>, new()
        where TPage : BaseTabsTabPage<TTab, TPage>, new()
    {
        private List<TTab> tabs;

        public BaseTabs()
        {
            this.tabs = new List<TTab>();
        }

        public IEnumerable<TPage> Pages
        {
            get
            {
                foreach (TTab tab in this.Tabs)
                {
                    yield return tab.Page;
                }
            }
        }

        public IEnumerable<TTab> Tabs { get { return this.tabs; } }

        public TTab Add()
        {
            TTab tab = new TTab();
            this.tabs.Add(tab);
            return tab;
        }
    }

    public abstract class BaseTabsTab<TTab, TPage>
        where TTab : BaseTabsTab<TTab, TPage>, new()
        where TPage : BaseTabsTabPage<TTab, TPage>, new()
    {
        public BaseTabsTab()
        {
            this.Page = new TPage();
            this.Page.Tab = (TTab)this;
        }

        public TPage Page { get; private set; }
    }

    public abstract class BaseTabsTabPage<TTab, TPage>
        where TTab : BaseTabsTab<TTab, TPage>, new()
        where TPage : BaseTabsTabPage<TTab, TPage>, new()
    {
        public BaseTabsTabPage()
        {
        }

        public TTab Tab { get; internal set; }
    }

    public class DefaultTab : BaseTabsTab<DefaultTab, DefaultTabPage> { }

    public class DefaultTabPage : BaseTabsTabPage<DefaultTab, DefaultTabPage> { }

    public class DefaultTabs : BaseTabs<DefaultTab, DefaultTabPage> { }
}

That provides strongly typed support for tabs and pages, both ways around, and allows new tabs with extended tabs or pages to be made. The 3 default classes allow you to consume it without getting involved in mucky generics decelerations where you don't need to:

DefaultTabs tabs = new DefaultTabs();
tabs.Add();
foreach (DefaultTab tab in tabs.Tabs) { }
foreach (DefaultTabPage page in tabs.Pages) { }

And it should also be clear to link back to the main tab container from either the tab or the page as well now.

Just thought I'd post the solution in case anyone else gets stuck with a similar chicken and egg generics problem.

Octopoid
  • 3,507
  • 5
  • 22
  • 43
0

Based on this SO Post, you can change the Example class to :

public class Example<TTab, TPage>  // 'TPage' must be a non-abstract type with a public parameterless constructor in order to use it as parameter 'TPage' in the generic type or method 'ExtendTabs.soExample.Tab<TPage>'
        where TTab : Tab<TPage>, new() 
        where TPage : TabPage<TTab>, new() //new() is provided here as a means to indicate constraint

    {
        private List<TTab> tabs;

        public Example()
        {
            this.tabs = new List<TTab>();

            this.tabs.Add(new TTab());
        }
    }
Community
  • 1
  • 1
Siva Gopal
  • 3,474
  • 1
  • 25
  • 22