views:

321

answers:

3

It's a design question. I have a business object, and 5 business object types that are derived from it.

I also will have a class which has BindingList as a member. I will have 5 classes derived from it.

Since covariance doesn't work here, how would you structure the design to minimize code repetition? I could of course chuck the BindingList and go with DataTable in which case the problem is eliminated.

But since everyone raves about BindingList I would LOVE to see how you guys would approach this.

SOLUTION (based on Pavel Minaev's answer):

public class SampleBase
    {
        protected string m_seq;
        protected string m_id;
        protected string m_weight;
        protected string m_units;       

        public SampleBase(string seq, string id, string weight, string units)
        {
            Seq = seq;
            Id = id;
            Weight = weight;
            Units = units;
        }

        public SampleBase() { }

        public string Seq
        {
            get { return m_seq; }
            set { m_seq = value; }
        }

        public string Id
        {
            get { return m_id; }
            set { m_id = value; }
        }

        public string Weight
        {
            get { return m_weight; }
            set { m_weight = value; }
        }

        public string Units
        {
            get { return m_units; }
            set { m_units = value; }
        }

    }

    public class FwdSample : SampleBase
    {
        protected string m_std_id;

        public FwdSample() { }

        public FwdSample (string seq, string id, string weight, string units, string std_id ) : base(seq, id, weight, units)
        {
            StdId = std_id;
        }

        public string StdId 
        {
            get { return m_std_id; }
            set { m_std_id = value; }
        }
    }
    //End of Sample Classes





    public abstract class RunBase<T> where T : SampleBase , new()
    {
        protected BindingList<T> m_samples;



        public RunBase() {}

        public void Add(T sample)
        {
            m_samples.Add(sample);
        }

        public void Update(int index, T sample)
        {
            m_samples[index] = sample;
        }

        public void Delete(int index)
        {
            m_samples.RemoveAt(index);
        }



        public BindingList<T> Samples
        {
            get { return m_samples; }
        }
    }


    public class FwdRun : RunBase<FwdSample>
    {

        public FwdRun()
        {
            m_samples = new BindingList<FwdSample>();
        }


    }
A: 

If you have an inheritance relationship between the children, why not use BindingList<TheBaseClass> as your main collection type?

The most prominent example of when you would need to use covariance is when you wanted to treat a BindingList<TheDerivedClass> as BindingList<TheBaseClass>. Can you give us a specific example of where this is tripping you up? Many of the scenarios for which coviarance is an anaswer can also be solved with a combination of generics, constraints and occasionally additional interfaces.

JaredPar
will try to explain. I have a grid with samples. There are 6 types of samples; each type has different columns in the grid (some are common to several types). So naturally, I created a SampleBase object and wanted to derive 6 types from that. Then I also have Run object which contains BindingList of SampleBase, since there are 6 types of run as well (type of sample = type of run for the sake of simplicity) - I have 6 objects that derived from RunBase. I may be over complicating it; but I am trying hard to have good design with minimal code repetition. I hope I am being clear.
gnomixa
oh and a grid can only display one run type(sample type) at a time. So say there are 100 samples displayed in the grid, there are all of the same type.
gnomixa
From your description, it sounds like you just need to make your `Run` class generic on the type of your business object.
Pavel Minaev
Pavel, that's what I was trying to do. But since I can't cast generics, how would I proceed? No one here is really giving me a clear answer. I might just stick with the DataTable.
gnomixa
One thing i forgot to mention as well is that there are 6 types of runs - each run treats the list of samples differently. That's what is basically tripping me up. My idea was to have a RunBase object that has a BindingList of SampleBase objects. Then each run/sample type inherits from RunBase and SampleBase respectively. Turned out I can't do that. So now I am stuck and I am not sure how to proceed. I am refactoring it by the way - I have DataTable (instead of BindingList) and a lot of switch statements right now for different types of runs/samples.
gnomixa
+3  A: 

Assuming your BindingList member is private (or protected), and isn't otherwise exposed in your class API, you'd probably want something like this:

  class Base
  {
      // No BindingList here. All members that don't need it should be here
      ...
  }

  class Base<TDerived> : Base where TDerived : Base<TDerived>
  {
       BindingList<TDerived> list = new BindingList<TDerived>();

       // All members that need BindingList should be here
  }

  class Derived1 : Base<Derived1> { ... }
  class Derived2 : Base<Derived2> { ... }
  ...
Pavel Minaev
it's not exposed in UI. I will take a look at this closely - brain is too fried now. thanks
gnomixa
I don't mean the UI here. "Clients of the class" means code that is creating and using objects of the class.
Pavel Minaev
+1 - I'm curious if there's a specific reason you chose to separate Base and Base<TDerived? It seems to me that Base<TDerived> where TDerived : Base<TDerived> would be sufficient.
dahlbyk
Mainly so that those members of `Base` that don't depend on `TDerived` can be in a single common base class, and algorithms that only need those members can just work with references of that class.
Pavel Minaev
Could you please explain the following:class Base<TDerived> : Base where TDerived : Base<TDerived>?
gnomixa
Dahlbyk's comment is utterly confusing me - isn't it exactly what Pavel wrote????
gnomixa
His comment was about the need for class `Base` (you can skip it, and put everything into `Base<TDerived>`, if you don't really need the common base class for your entities).
Pavel Minaev
The type constraint simply says that `TDerived` must be a class that inherits from `Base<TDerived>`. In other words, it enforces the recursive pass-itself-to-base-class-type-parameter pattern that is used here.
Pavel Minaev
Sorry, Pavel. I am just not getting it - why are you making it so complex? I just want a simple clean solution; is there a real valid reason to make it so complicated?
gnomixa
If you find it complex, then simply do not use the generic type constraint.
Pavel Minaev
I got it. I did something similar to your suggestion, only simpler. I will post the solution in the body of the question.
gnomixa
+1  A: 

This example only works with .net 3.5 or higher. :(

Perhaps a property that returns all of the inherited objects. I had a similar question and using System.Linq. Here is what I used:

List<A> testme = new List<B>().OfType<A>().ToList();

Or cast all of them to the parent:

List<A> testme = new List<B>().Cast<A>().ToList();

The above code was from this answer. Thanks Matt.

daub815
I just checked on msdn and apparently System.Linq is not supported in Compact Framework.
gnomixa