Generic collections and inheritance

  • Thread starter Thread starter Lars
  • Start date Start date
L

Lars

Hi,

I have a base class holding a generic list that needs
to be accessed by both the base class and its subclasses.
What is the best solution to this?

I am fairly new to generics, but I am aware of that fact
that if you have a class B, that inherits from A, then
List<B> does NOT inherit from List<A>. So I understand
why the example below does not compile, but I fail to
see how to solve the problem stated above... For example,
I need to be able to return a BindingList<LeaseContractLine>
from the LeaseContract class below.


// Sample:
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Windows.Forms;

namespace GenericInheritanceTest
{
public partial class Form1 : Form
{
// A simple form with only a DataGridView on it:
public Form1()
{
InitializeComponent();
}

private void Form1_Load(object sender, EventArgs e)
{
LeaseContract leaseContract = new LeaseContract();
// Display the contract lines in a DataGridView:
uxLeaseContractGrid.DataSource = leaseContract.GetLines();
}
}

public class Contract
{
protected BindingList<ContractLine> _lines = new
BindingList<ContractLine>();
public Contract() { }
//
// methods providing functionality for all types of contracts...
//
}

public class LeaseContract : Contract
{
public LeaseContract() { }

public BindingList<LeaseContractLine> GetLines()
{
// Will not compile (since the cast is not valid):
return (BindingList<LeaseContractLine>)_lines;
}
//
// specific LeaseContract methods, etc.
//
}

public class ContractLine
{
public ContractLine() { }
}


public class LeaseContractLine : ContractLine
{
public LeaseContractLine() { }
}
}


Any help would be greatly appreicated!

Thanks,
Lars
 
I would try making Contract itself a generic class:
Something like this:

public class Contract<T> where T : ContractLine
{
protected BindingList<T> _lines = new BindingList<T>();
}

public class LeaseContract : Contract<LeaseContractLine>
{
....
}
 
Adam Clauss said:
I would try making Contract itself a generic class:
Something like this:

public class Contract<T> where T : ContractLine
{
protected BindingList<T> _lines = new BindingList<T>();
}

public class LeaseContract : Contract<LeaseContractLine>
{
...
}

Thanks for the reply - that seems to solve that problem. When
trying to implement this in the actual application, I come
across another problem, though: The ContractLine class holds
a reference to the parent (the Contract class). Trying to
assign to this reference gives a compile error in AddLine() below:
"Cannot convert type 'Contract<T>' to 'Contract<ContractLine>'"
(abbreviated message).

Here is the updated sample code:

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Windows.Forms;

namespace GenericInheritanceTst
{
public partial class Form2 : Form
{
public Form2()
{
InitializeComponent();
}
}

// Contract & ContractLine class:
public class Contract<T> where T : ContractLine, new()
{
protected BindingList<T> _lines = new BindingList<T>();
public Contract() { }

public void AddLine()
{
T contractLine = new T();
// Won't compile ("Cannot convert type 'Contract<T>' to
'Contract<ContractLine>'"):
contractLine.Parent = this;
_lines.Add(contractLine);
}
}

public class ContractLine
{
public Contract<ContractLine> Parent;
public ContractLine() { }
}

// LeaseContract & LeaseContractLine class:
public class LeaseContract : Contract<LeaseContractLine>
{
public LeaseContract() { }

public BindingList<LeaseContractLine> GetLines()
{
return _lines;
}
}

public class LeaseContractLine : ContractLine
{
public LeaseContractLine() { }
}
}

What am I missing?

Thanks,
Lars
 
Lars said:
Thanks for the reply - that seems to solve that problem. When
trying to implement this in the actual application, I come
across another problem, though: The ContractLine class holds
a reference to the parent (the Contract class). Trying to
assign to this reference gives a compile error in AddLine() below:
"Cannot convert type 'Contract<T>' to 'Contract<ContractLine>'"
(abbreviated message).

Heh... incidentally someone else is trying to do something very similar.
See the post by Kris Jennings (dated 07/21/2006 8:51pm) - more specifically
see the reply to that by Barry Kelly (9:28pm).

In any event, the ContractLine probably also needs to be generic.

namespace GenericInheritanceTst
{
public partial class Form2 : Form
{
public Form2()
{
InitializeComponent();
}
}

// Contract & ContractLine class:
public class Contract<T> where T : ContractLine<T>, new()
{
protected BindingList<T> _lines = new BindingList<T>();
public Contract() { }

public void AddLine()
{
T contractLine = new T();
contractLine.Parent = this;
_lines.Add(contractLine);
}
}

public class ContractLine<LineBase> where LineBase :
ContractLine<LineBase>, new()
{
public Contract<ContractLine> Parent;
public ContractLine() { }
}

// LeaseContract & LeaseContractLine class:
public class LeaseContract : Contract<LeaseContractLine>
{
public LeaseContract() { }

public BindingList<LeaseContractLine> GetLines()
{
return _lines;
}
}

public class LeaseContractLine : ContractLine<LeaseContractLine>
{
public LeaseContractLine() { }
}
}


Generics make for very good... generic code. But they can be difficult to
wrap your mind around at times.
 
Also - unless you particularly only want "GetLines" to be in LeaseContract,
with generics you can now abstract it up to the base class:

// Contract & ContractLine class:
public class Contract<T> where T : ContractLine<T>, new()
{
protected BindingList<T> _lines = new BindingList<T>();
public Contract() { }

public void AddLine()
{
T contractLine = new T();
contractLine.Parent = this;
_lines.Add(contractLine);
}

public BindingList<T> GetLines()
{
return _lines;
}
}

That should still allow what you need:
LeaseContract contract = ....
BindingList<LeaseContractLine> lines = contract.GetLines();

And immediately support any future forms of Contract/ContractLine pairs.
 
In any event, the ContractLine probably also needs to be generic.
public class ContractLine<LineBase> where LineBase :
ContractLine<LineBase>, new()
{

Thanks again Adam - that got me one step closer. But with a
recursive constraint for ContractLine:
public class ContractLine<T> where T : ContractLine<T>, new()

how do you declare a reference to it? This does not compile:
List<Contract<ContractLine>> contracts = new List<Contract<ContractLine>>();

You get "Using the generic type 'ContractLine<T>' requires '1' type
arguments"
(and List<Contract<ContractLine<ContractLine>>> of course will not either)

Updated sample:

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Windows.Forms;

namespace GenericInheritanceTst
{
public partial class Form2 : Form
{
public Form2()
{
InitializeComponent();
// Error: Using the generic type 'ContractLine<T>' requires '1'
type arguments:
List<Contract<ContractLine>> contracts = new
List<Contract<ContractLine>>();
}
}

// Contract & ContractLine class:
public class Contract<T> where T : ContractLine<T>, new()
{
protected BindingList<T> _lines = new BindingList<T>();
public Contract() { }

public void AddLine()
{
T contractLine = new T();
contractLine.Parent = this;
_lines.Add(contractLine);
}
}

public class ContractLine<T> where T : ContractLine<T>, new()
{
public Contract<T> Parent;
public ContractLine() { }
}

// LeaseContract & LeaseContractLine class:
public class LeaseContract<T> where T : LeaseContractLine<T>, new()
{
public LeaseContract() { }
}

public class LeaseContractLine<T> where T : LeaseContractLine<T>, new()
{
public LeaseContractLine() { }
}
}

Thanks,
Lars
 
In any event, the ContractLine probably also needs to be generic.
public class ContractLine<LineBase> where LineBase :
ContractLine<LineBase>, new()
{

Thanks again Adam - that got me one step closer. But with a
recursive constraint for ContractLine:
public class ContractLine<T> where T : ContractLine<T>, new()

how do you declare a reference to it? This does not compile:
List<Contract<ContractLine>> contracts = new List<Contract<ContractLine>>();

You get "Using the generic type 'ContractLine<T>' requires '1' type
arguments"
(and List<Contract<ContractLine<ContractLine>>> of course will not either)

Updated sample:

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Windows.Forms;

namespace GenericInheritanceTst
{
public partial class Form2 : Form
{
public Form2()
{
InitializeComponent();
// Error: Using the generic type 'ContractLine<T>' requires '1'
type arguments:
List<Contract<ContractLine>> contracts = new
List<Contract<ContractLine>>();
}
}

// Contract & ContractLine class:
public class Contract<T> where T : ContractLine<T>, new()
{
protected BindingList<T> _lines = new BindingList<T>();
public Contract() { }

public void AddLine()
{
T contractLine = new T();
contractLine.Parent = this;
_lines.Add(contractLine);
}
}

public class ContractLine<T> where T : ContractLine<T>, new()
{
public Contract<T> Parent;
public ContractLine() { }
}

// LeaseContract & LeaseContractLine class:
public class LeaseContract<T> where T : LeaseContractLine<T>, new()
{
public LeaseContract() { }
}

public class LeaseContractLine<T> where T : LeaseContractLine<T>, new()
{
public LeaseContractLine() { }
}
}

Thanks,
Lars
 
Lars said:
Thanks again Adam - that got me one step closer. But with a
recursive constraint for ContractLine:
public class ContractLine<T> where T : ContractLine<T>, new()

how do you declare a reference to it? This does not compile:
List<Contract<ContractLine>> contracts = new
List<Contract<ContractLine>>();

You get "Using the generic type 'ContractLine<T>' requires '1' type
arguments"
(and List<Contract<ContractLine<ContractLine>>> of course will not either)

Hrm... thats a very good question hehe... one I'm afraid I do not know the
answer to.
I've posted a followup to Barry in that other thread to see if he has a
suggestion.
 
OK, this coming from suggestions by Barry and Larry in that other thread. Try extracting the public members of Contract into an
interface (IContract) which Contract would implement. Then your list would just be:

List<IContract> contracts = new List<IContract>();
 
OK, this coming from suggestions by Barry and Larry in that other thread.
Try extracting the public members of Contract into an interface
(IContract) which Contract would implement. Then your list would just be:

List<IContract> contracts = new List<IContract>();

Thanks for hanging in there, Adam.

I am not sure I follow the last part - if I use an interface
for IContract (or IContractLine as Barry & Larry suggested),
how will that interface represent both the base class and the
subclasses derived from it? Would I have to push all my
subclassing hierarchies into interfaces? Also, when I tried
doing this in the sample, I realized that I do not understand
quite how to do this. Could you show me an example?

Thanks,
Lars

Ps. What I am trying to do should be a fairly common
scenario, so I am surprised at the amount of complexity
generics seem to add to it. Not sure if they are worth
the added complexity... Any thoughts on this?
 
Lars said:
Thanks for hanging in there, Adam.

Not a problem, I'm still trying to get a better handle on generics and how everythign works together with them myself, so I am
almost as interested in the solution as you (almost because I don't have an immediate need for it :) )
I am not sure I follow the last part - if I use an interface
for IContract (or IContractLine as Barry & Larry suggested),
how will that interface represent both the base class and the
subclasses derived from it? Would I have to push all my
subclassing hierarchies into interfaces? Also, when I tried
doing this in the sample, I realized that I do not understand
quite how to do this. Could you show me an example?

OK took a stab at our example code. Note following changes
Addition of interfaces IContract/IContractLine. Contract inherits from IContract and ContractLine inherits from IContractLine.
Also - the LeaseContract and LeaseContractLine classes do NOT need to be generic (as you had them in your previous post).

namespace GenericInheritanceTst
{
public partial class Form2 : Form
{
public Form2()
{
List<IContract> contracts = new List<IContract>();
}
}

interface IContract
{
void AddLine();
}

interface IContractLine
{
//not sure what kind of public methods should go here?
//You may not need an interface for IContractLine if these never make it to a generic collection like Contract does.
}

// Contract & ContractLine class:
public class Contract<T> : IContract where T : ContractLine<T>, new()
{
protected BindingList<T> _lines = new BindingList<T>();
public Contract() { }

public void AddLine()
{
T contractLine = new T();
contractLine.Parent = this;
_lines.Add(contractLine);
}
}

public class ContractLine<T> : IContractLine where T : ContractLine<T>, new()
{
public Contract<T> Parent;
public ContractLine() { }
}

// LeaseContract & LeaseContractLine class:
public class LeaseContract : Contract<LeaseContractLine>
{
public LeaseContract() { }
}

public class LeaseContractLine : ContractLine<LeaseContractLine>
{
public LeaseContractLine() { }
}
}
Ps. What I am trying to do should be a fairly common
scenario, so I am surprised at the amount of complexity
generics seem to add to it. Not sure if they are worth
the added complexity... Any thoughts on this?

Well, I agree it does have a bit of complexity. But I think it's more of a "once you figure it out" for the first time, it gets
easier.
 
OK took a stab at our example code. Note following changes
Addition of interfaces IContract/IContractLine. Contract inherits from
IContract and ContractLine inherits from IContractLine.
Also - the LeaseContract and LeaseContractLine classes do NOT need to be
generic (as you had them in your previous post).

Ok. I like the fact of not having to make LeaseContract
generic, since it makes for clear code. To take this
one step further, I was thinking of using an abstract
base class that is generic and have non-generic sub-
classes from that. In this way, the interface of the
Contract & ContractLine class can stay the same and
I do not have to change all the classes that are already
using them.

So I have ContractBase<T> and from that I create the
subclass Contract as follows:
public class Contract : ContractBase<ContractLine>

But then I want to create LeaseContract that should
be a subclass of Contract, but if I do that, I of course
lose the ability to specify that T should be
LeaseContractLine:
public class LeaseContract : Contract

Instead I have to do this:
public class LeaseContract : ContractBase<LeaseContractLine>
But that means I can only subclass directly from the
generic base class.

Is there any way to keep the "genericness" of the contract
classes from having to be exposed in their interface (and
with "interface" I mean "publicly exposed properties &
methods")? It would make the code using these classes
much more clear & readable, and I would not have to
refactor all my existing code using these classes.

Updated sample:

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Text;
using System.Windows.Forms;

namespace GenericInheritanceTest4
{
public partial class Form4 : Form
{
public Form4()
{
InitializeComponent();
}
}

interface IContract
{
void AddLine();
}

interface IContractLine
{
//not sure what kind of public methods should go here?
//You may not need an interface for IContractLine if these never
make it to a generic collection like Contract does.
}

// ContractBase & ContractLineBase class:
public abstract class ContractBase<T> : IContract where T :
ContractLineBase<T>, new()
{
public int ContractTestProperty;
protected BindingList<T> _lines = new BindingList<T>();
public ContractBase() { }

public void AddLine()
{
T contractLine = new T();
contractLine.Parent = this;
_lines.Add(contractLine);
}
}

public abstract class ContractLineBase<T> : IContractLine where T :
ContractLineBase<T>, new()
{
public ContractBase<T> Parent;
public ContractLineBase() { }
}

// Contract & ContractLine class:
public class Contract : ContractBase<ContractLine>
{
public int LeaseTestProperty;
public Contract() { }
}

public class ContractLine : ContractLineBase<ContractLine>
{
public ContractLine() { }
}

// LeaseContract & LeaseContractLine class:
public class LeaseContract : ContractBase<LeaseContractLine>
{
public int LeaseTestProperty;
public LeaseContract() { }
}

public class LeaseContractLine : ContractLineBase<LeaseContractLine>
{
public LeaseContractLine() { }
}
}

Thanks again,
Lars
 
OK took a stab at our example code. Note following changes
Addition of interfaces IContract/IContractLine. Contract inherits from
IContract and ContractLine inherits from IContractLine.
Also - the LeaseContract and LeaseContractLine classes do NOT need to be
generic (as you had them in your previous post).

I started to try to implement this and came accross another
problem: You cannot instantiate a Contract directly:

This does not compile:
IContract newContract = new Contract<IContractLine>();
You get "IContractLine must have a public parameterless
constructor in order to use it as parameter 'T' in the
generic type or method 'Contract<T>'," since an interface
does not have a constructor.

This compiles just fine, though:
LeaseContract leaseContract = new LeaseContract();

It seems like you have to make the generic class an abstract
base class and subclass it with non-generic classes. The
only problem is how to subclass further from such a subclass
(see my previous post in this thread).


Sample:

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Windows.Forms;

namespace GenericInheritanceTst2
{
public partial class Form3 : Form
{
public Form3()
{
InitializeComponent();
// Works fine with a collection:
List<IContract> contracts = new List<IContract>();

// But how do you create a reference to a single contract??:
IContract newContract = new Contract<IContractLine>();

// Creating a reference to LeaseContract works just fine:
LeaseContract leaseContract = new LeaseContract();
}
}

public interface IContract
{
void AddLine();
BindingList<IContractLine> GetLines();
}

public interface IContractLine
{
IContract Parent { get; set; }
}

// Contract & ContractLine class:
public class Contract<T> : IContract where T : IContractLine, new()
{
public int ContractTestProperty;
protected BindingList<T> _lines = new BindingList<T>();
public Contract() { }

public void AddLine()
{
T contractLine = new T();
contractLine.Parent = this;
_lines.Add(contractLine);
}

public BindingList<IContractLine> GetLines()
{
return new BindingList<IContractLine>();
}
}

public class ContractLine<T> : IContractLine where T : IContractLine,
new()
{
protected IContract _parent;
public IContract Parent
{
get { return _parent; }
set { _parent = value; }
}

public ContractLine() { }
}

// LeaseContract & LeaseContractLine class:
public class LeaseContract : Contract<LeaseContractLine>
{
public int LeaseTestProperty;
public LeaseContract() { }
}

public class LeaseContractLine : ContractLine<LeaseContractLine>
{
public LeaseContractLine() { }
}
}

Thanks,
Lars
 
Adam,
I am sorry to bombard you, but I came accross a third issue
that seems to put me back on square one:

In the Contract class, I have GetLines which is defined
using <T> to work right:
public BindingList<T> GetLines()
{
return _lines;
}

But how do you define this method in the IContract interface??
(you can obviously not use <T> there...).

Thanks,
Lars

Ps. My sample in the last post uses
"BindingList<IContractLine> GetLines()",
but that obviously does not work since I need
generic behavior in the BindingList.
 
Lars said:
I have a base class holding a generic list that needs
to be accessed by both the base class and its subclasses.
What is the best solution to this?

Looking at your source code, I see you basically need two hierarchies:

1) for your container, e.g. LeaseContract <: Contract
2) for your lines, e.g. LeaseContractLine <: ContractLine

.... and when you inherit from your base contract, you want to now be
able to add new lines. Well, the first thing is - you can't do that
trivially, as you already know. If Contract had any kind of list that
allowed adding arbitrary ContractLine descendants, then you could get
contract lines from other branches of the tree - say,
"MurderContractLine" - that don't belong in a LeaseContract. This is the
basic covariance problem.

a) Now, a question you've got to answer: do you still need to be able to
add arbitrary ContractLine instances to a LeaseContract?

b) Next, in what way would a Contract be a subtype of a LeaseContract
when the two don't support adding the same kinds of lines? This is an
instance of the covariance problem. (Adding a Cat to a List<Dog> held in
a variable typed List<Mammal>). This gives rise to the second question:
what kind of polymorphism / subtyping do you need for Contract
descendants? Would an IContract interface be sufficient?

Can you answer these two questions first? Just to be clear on the
subtyping relationship you need for the two parallel hierarchies
involved.

-- Barry
 
I have a base class holding a generic list that needs
Looking at your source code, I see you basically need two hierarchies:
1) for your container, e.g. LeaseContract <: Contract
2) for your lines, e.g. LeaseContractLine <: ContractLine

a) Now, a question you've got to answer: do you still need to be able to
add arbitrary ContractLine instances to a LeaseContract?

No. If I create a LeaseContract, I would only add LeaseContractLines
to it.
b) Next, in what way would a Contract be a subtype of a LeaseContract
when the two don't support adding the same kinds of lines? This is an
instance of the covariance problem. (Adding a Cat to a List<Dog> held in
a variable typed List<Mammal>). This gives rise to the second question:
what kind of polymorphism / subtyping do you need for Contract
descendants? Would an IContract interface be sufficient?

A LeaseContract would need to inherit the methods, properties, etc. of
a Contract. It would be like any regular subclassing where you can
reuse the functionality of the base class. Some applications use
Contract classes and some use LeaseContract classes.

An application that uses a Contract class or a LeaseContract class
will need to be able to create a reference to a Contract or
LeaseContract or collections of Contracts or LeaseContracts,
as well as retrieving their ContractLines or LeaseContractLines as
BindingList<T> (for grids). The application that uses the
ContractClass, etc. also needs to be able to create new
ContractLines or LeaseContractLines to be added to the Contract, or
LeaseContract.

Thanks for your help,
Lars
 
Lars said:
No. If I create a LeaseContract, I would only add LeaseContractLines
to it.


A LeaseContract would need to inherit the methods, properties, etc. of
a Contract. It would be like any regular subclassing where you can
reuse the functionality of the base class. Some applications use
Contract classes and some use LeaseContract classes.

An application that uses a Contract class or a LeaseContract class
will need to be able to create a reference to a Contract or
LeaseContract or collections of Contracts or LeaseContracts,
as well as retrieving their ContractLines or LeaseContractLines as
BindingList<T> (for grids). The application that uses the
ContractClass, etc. also needs to be able to create new
ContractLines or LeaseContractLines to be added to the Contract, or
LeaseContract.

OK. As far as I understand it, you need to have a Contract which
supports databinding via your GetLines(), using BindingList<T>.
(Disclaimer: I don't know much about BindingList<T>.)

So, it seems to me, there is a little trouble in these requirements
because of the mixing of dynamic polymorphism (inheritance) and
parametric polymorphism (generics). It's best to make GetLines()
abstract and have it return an object, so that the correct generic
instance can be returned in descendants. This is only one of the many
reasons DataGridView.DataSource is an Object, I'm guessing.

So, to get polymorphic behaviour with classes, we need an abstract base
class - I'll call it Contract.

In order to support the strongly-typed lines via generics, we'll need a
second base class, called Contract<T>.

Finally, I'll assume that you require your lines to have a
back-reference to the contract. With those constraints in mind, here's
one possible solution:

---8<---
abstract class Contract
{
public abstract object BindingList { get; }
}

abstract class Contract<TLine> : Contract
where TLine : ContractLine<TLine>
{
private BindingList<TLine> _list;

public override object BindingList { get { return _list; } }

// Whenever needed, you can assign to TLine.Contract because
// it's guaranteed to derive from ContractLine<TLine>
}

abstract class ContractLine<TLine>
where TLine : ContractLine<TLine>
{
public Contract<TLine> Contract { get {...} internal set {...} }
}
--->8---

-- Barry
 
OK. As far as I understand it, you need to have a Contract which
supports databinding via your GetLines(), using BindingList<T>.
(Disclaimer: I don't know much about BindingList<T>.)

So, it seems to me, there is a little trouble in these requirements
because of the mixing of dynamic polymorphism (inheritance) and
parametric polymorphism (generics). It's best to make GetLines()
abstract and have it return an object, so that the correct generic
instance can be returned in descendants. This is only one of the many
reasons DataGridView.DataSource is an Object, I'm guessing.

So, to get polymorphic behaviour with classes, we need an abstract base
class - I'll call it Contract.

In order to support the strongly-typed lines via generics, we'll need a
second base class, called Contract<T>.

Finally, I'll assume that you require your lines to have a
back-reference to the contract. With those constraints in mind, here's
one possible solution:

Thanks for your advice Barry. At a first glance, I see possible problems
returning an object instead of a BindingList in GetLines. AddLine also
has the same problem, and making it accept anything of type Object
would throw type safety out the window.

Anyhow, I have decided that generics are not suitable for the purpose,
having spent days banging my head (and others') on this. There
probably is a way to make it work, but it is doubtful whether it is
worth the added complexity.

Instead I am going to refactor back to use a non-generic ArrayList
or similar for the lines in the Contract class and write wrapper classes
for type-safety (the "old" .NET 1.1-way).

I'll probably visit this topic again at some point - I would hate
to throw generics out the window permanently, since they do provide
some really neat benefits.

Thanks again,
Lars
 
Lars said:
Thanks for your advice Barry. At a first glance, I see possible problems
returning an object instead of a BindingList in GetLines.

That property is strictly for the DataSource in the WinForms control.
You can return a strongly-typed version in a different method / property
in the Contract<TLine> class.

The idea between having two classes, Contract and Contract<TLine>, is
that you unify all the generic descendants under one hierarchy so that
you have appropriate places for runtime polymorphism (Contract) and
strongly-typed generic stuff (Contract said:
AddLine also
has the same problem, and making it accept anything of type Object
would throw type safety out the window.

Can you afford the time to talk further about this? AddLine() would work
perfectly well in this scenario in the Contract<TLine> class, where you
add a 'new()' constraint to TLine in both Contract<TLine> and
ContractLine said:
Anyhow, I have decided that generics are not suitable for the purpose,
having spent days banging my head (and others') on this. There
probably is a way to make it work, but it is doubtful whether it is
worth the added complexity.

No worries.
I'll probably visit this topic again at some point - I would hate
to throw generics out the window permanently, since they do provide
some really neat benefits.

Sure. I just think you might be giving up too soon :)

-- Barry
 
Thanks for your advice Barry. At a first glance, I see possible problems
That property is strictly for the DataSource in the WinForms control.
You can return a strongly-typed version in a different method / property
in the Contract<TLine> class.
Ok, that should work.
Can you afford the time to talk further about this? AddLine() would work
perfectly well in this scenario in the Contract<TLine> class, where you
add a 'new()' constraint to TLine in both Contract<TLine> and
ContractLine<TLine>.
Yes, at a second look, you are absolutely right.
Sure. I just think you might be giving up too soon :)
I just cannot afford to hold up the project I am working on any longer,
so I have now refactored it to use a ContractLineCollection class
that inherits from CollectionBase, instead, and all works beautifully.

But I still would like to get to the bottom of this generic issue. I have
rewritten the sample following your suggestions. Instead of naming
the base classes Contract and Contract<T>, I have named them
ContractBase and ContractBase<T>. Here is the updated sample:

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Windows.Forms;

namespace GenericInheritanceTest5
{
public partial class Form5 : Form
{
public Form5()
{
InitializeComponent();

LeaseContract contract = new LeaseContract();
contract.AddLine(new LeaseContractLine());
contract.AddLine(new LeaseContractLine());
uxDataGrid.DataSource = contract.BindingList2;
}
}

// Is ContractBase really needed??
public abstract class ContractBase
{
public abstract object BindingList { get; }
}

public abstract class ContractBase<TLine> : ContractBase
where TLine : ContractLineBase<TLine>, new()
{
private BindingList<TLine> _lines = new BindingList<TLine>();
public override object BindingList { get { return _lines; } }
// BindingList2 seems to work as well as BindingList:
public BindingList<TLine> BindingList2 { get { return _lines; } }

public void AddLine(TLine line)
{
line.Parent = this;
_lines.Add(line);
}
}

public abstract class ContractLineBase<TLine>
where TLine : ContractLineBase<TLine>, new()
{
public ContractBase<TLine> Parent; // public for brevity.
}

// Contract & ContractLine class:
public class Contract : ContractBase<ContractLine>
{
public Contract() { }
}

public class ContractLine : ContractLineBase<ContractLine>
{
public int ContractLineTestProperty { get { return 1; } }
public ContractLine() { }
}

// LeaseContract & LeaseContractLine class:
public class LeaseContract : Contract
{
public LeaseContract() { }
}

public class LeaseContractLine : ContractLine
{
public int LeaseContractLineTestProperty { get { return 2; } }
public LeaseContractLine() { }
}
}

Note that LeaseContract needs to inherit from Contract, but as
coded above it does not work right since the line types then
becomes ContractLine and not LeaseContractLine. I basically
need to be able to subclass more than one level from the
abstract base class.

Also, I don't quite see why "public abstract class ContractBase"
is needed. I have added a "BindingList2" above to demonstrate
(you can bind either BindingList or BindingList2 to the
grid with no apparent difference in the result).

Thanks,
Lars
 

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Back
Top